JS: marshall JS object to Java in a wrapper

This commit is contained in:
Alexey Andreev 2023-07-20 22:13:50 +02:00
parent 9438380716
commit 948244cbf4
26 changed files with 1468 additions and 255 deletions

View File

@ -47,6 +47,7 @@ import org.teavm.interop.Unmanaged;
import org.teavm.jso.core.JSArray;
import org.teavm.platform.Platform;
import org.teavm.platform.PlatformClass;
import org.teavm.platform.PlatformObject;
import org.teavm.platform.PlatformSequence;
import org.teavm.runtime.RuntimeClass;
import org.teavm.runtime.RuntimeObject;
@ -402,7 +403,7 @@ public class TClass<T> extends TObject implements TAnnotatedElement, TType {
@InjectedBy(ClassGenerator.class)
@PluggableDependency(ClassGenerator.class)
public native <T> T newEmptyInstance();
public native PlatformObject newEmptyInstance();
@SuppressWarnings({ "raw", "unchecked" })
public TConstructor<?>[] getDeclaredConstructors() throws TSecurityException {

View File

@ -23,8 +23,6 @@ import org.teavm.classlib.java.lang.TIllegalAccessException;
import org.teavm.classlib.java.lang.TIllegalArgumentException;
import org.teavm.classlib.java.lang.TInstantiationException;
import org.teavm.classlib.java.lang.TObject;
import org.teavm.platform.PlatformObject;
import org.teavm.platform.PlatformSequence;
public class TConstructor<T> extends TAccessibleObject implements TMember {
private TClass<T> declaringClass;
@ -113,8 +111,8 @@ public class TConstructor<T> extends TAccessibleObject implements TMember {
}
}
PlatformSequence<PlatformObject> jsArgs = Converter.arrayFromJava(initargs);
PlatformObject instance = declaringClass.newEmptyInstance();
var jsArgs = Converter.arrayFromJava(initargs);
var instance = declaringClass.newEmptyInstance();
callable.call(instance, jsArgs);
return (T) Converter.toJava(instance);
}

View File

@ -233,6 +233,7 @@ abstract class AbstractInstructionAnalyzer extends AbstractInstructionReader {
if (className != null) {
getAnalyzer().linkClass(className);
}
getAnalyzer().linkClass("java.lang.ClassCastException");
}
@Override

View File

@ -223,6 +223,7 @@ class DependencyGraphBuilder {
@Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
super.cast(receiver, value, targetType);
currentExceptionConsumer.consume(dependencyAnalyzer.getType("java.lang.ClassCastException"));
DependencyNode valueNode = nodes[value.getIndex()];
DependencyNode receiverNode = nodes[receiver.getIndex()];
ClassReaderSource classSource = dependencyAnalyzer.getClassSource();

View File

@ -23,7 +23,6 @@ import java.util.HashMap;
import java.util.Map;
import org.teavm.common.ServiceRepository;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.BasicBlockReader;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
@ -32,7 +31,6 @@ import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ProgramReader;
import org.teavm.model.ReferenceCache;
import org.teavm.model.TryCatchBlockReader;
import org.teavm.model.ValueType;
public class FastDependencyAnalyzer extends DependencyAnalyzer {
@ -59,12 +57,12 @@ public class FastDependencyAnalyzer extends DependencyAnalyzer {
ProgramReader program = method.getProgram();
if (program != null) {
FastInstructionAnalyzer instructionAnalyzer = new FastInstructionAnalyzer(this);
var instructionAnalyzer = new FastInstructionAnalyzer(this);
instructionAnalyzer.setCaller(method.getReference());
for (BasicBlockReader block : program.getBasicBlocks()) {
for (var block : program.getBasicBlocks()) {
block.readAllInstructions(instructionAnalyzer);
for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) {
for (var tryCatch : block.readTryCatchBlocks()) {
if (tryCatch.getExceptionType() != null) {
linkClass(tryCatch.getExceptionType());
}

View File

@ -0,0 +1,342 @@
/*
* 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.model.analysis;
import com.carrotsearch.hppc.IntStack;
import java.util.ArrayDeque;
import java.util.Objects;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.model.InvokeDynamicInstruction;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.ArrayLengthInstruction;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.BinaryInstruction;
import org.teavm.model.instructions.BoundCheckInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.CastIntegerInstruction;
import org.teavm.model.instructions.CastNumberInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.CloneArrayInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.DoubleConstantInstruction;
import org.teavm.model.instructions.FloatConstantInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.IsInstanceInstruction;
import org.teavm.model.instructions.LongConstantInstruction;
import org.teavm.model.instructions.NegateInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
public abstract class BaseTypeInference<T> {
private Program program;
private MethodReference reference;
private Object[] types;
private Graph graph;
private Graph arrayGraph;
public BaseTypeInference(Program program, MethodReference reference) {
this.program = program;
this.reference = reference;
}
private void prepare() {
types = new Object[program.variableCount()];
var visitor = new InitialTypeVisitor(program.variableCount());
var params = Math.min(reference.parameterCount(), program.variableCount() - 1);
for (var i = 0; i < params; ++i) {
visitor.type(program.variableAt(i + 1), reference.parameterType(i));
}
visitor.type(program.variableAt(0), ValueType.object(reference.getClassName()));
for (var block : program.getBasicBlocks()) {
for (var insn : block) {
insn.acceptVisitor(visitor);
}
for (var phi : block.getPhis()) {
for (var incoming : phi.getIncomings()) {
visitor.graphBuilder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex());
}
}
}
graph = visitor.graphBuilder.build();
arrayGraph = visitor.arrayGraphBuilder.build();
}
@SuppressWarnings("unchecked")
private void propagate() {
var stack = new IntStack();
var typeStack = new ArrayDeque<T>();
for (var i = 0; i < types.length; ++i) {
if (types[i] != null) {
stack.push(i);
typeStack.push((T) types[i]);
types[i] = null;
}
}
while (!stack.isEmpty()) {
var variable = stack.pop();
var type = typeStack.pop();
var formerType = (T) types[variable];
if (Objects.equals(formerType, type)) {
continue;
}
type = doMerge(type, formerType);
if (Objects.equals(type, formerType)) {
continue;
}
types[variable] = type;
for (var succ : graph.outgoingEdges(variable)) {
if (!Objects.equals(types[succ], type)) {
stack.push(succ);
typeStack.push(type);
}
}
if (arrayGraph.outgoingEdgesCount(variable) > 0) {
var elementType = elementType(type);
for (var succ : arrayGraph.outgoingEdges(variable)) {
if (!Objects.equals(types[succ], elementType)) {
stack.push(succ);
typeStack.push(elementType);
}
}
}
}
}
public void ensure() {
if (types == null) {
prepare();
propagate();
}
}
@SuppressWarnings("unchecked")
public T typeOf(Variable variable) {
ensure();
return (T) types[variable.getIndex()];
}
protected abstract T mapType(ValueType type);
protected abstract T nullType();
private T doMerge(T a, T b) {
if (a == null) {
return b;
} else if (b == null) {
return a;
} else {
return merge(a, b);
}
}
protected abstract T merge(T a, T b);
protected abstract T elementType(T t);
private class InitialTypeVisitor extends AbstractInstructionVisitor {
private GraphBuilder graphBuilder;
private GraphBuilder arrayGraphBuilder;
InitialTypeVisitor(int size) {
graphBuilder = new GraphBuilder(size);
arrayGraphBuilder = new GraphBuilder(size);
}
@Override
public void visit(NullConstantInstruction insn) {
types[insn.getReceiver().getIndex()] = nullType();
}
@Override
public void visit(IntegerConstantInstruction insn) {
type(insn.getReceiver(), ValueType.INTEGER);
}
@Override
public void visit(LongConstantInstruction insn) {
type(insn.getReceiver(), ValueType.LONG);
}
@Override
public void visit(FloatConstantInstruction insn) {
type(insn.getReceiver(), ValueType.FLOAT);
}
@Override
public void visit(DoubleConstantInstruction insn) {
type(insn.getReceiver(), ValueType.DOUBLE);
}
@Override
public void visit(ClassConstantInstruction insn) {
type(insn.getReceiver(), ValueType.object("java/lang/Class"));
}
@Override
public void visit(StringConstantInstruction insn) {
type(insn.getReceiver(), ValueType.object("java/lang/String"));
}
@Override
public void visit(ConstructInstruction insn) {
type(insn.getReceiver(), ValueType.object(insn.getType()));
}
@Override
public void visit(ConstructArrayInstruction insn) {
type(insn.getReceiver(), ValueType.arrayOf(insn.getItemType()));
}
@Override
public void visit(ConstructMultiArrayInstruction insn) {
var type = insn.getItemType();
for (var i = 0; i < insn.getDimensions().size(); ++i) {
type = ValueType.arrayOf(type);
}
type(insn.getReceiver(), type);
}
@Override
public void visit(IsInstanceInstruction insn) {
type(insn.getReceiver(), ValueType.BOOLEAN);
}
@Override
public void visit(CastInstruction insn) {
type(insn.getReceiver(), insn.getTargetType());
}
@Override
public void visit(NegateInstruction insn) {
type(insn.getReceiver(), insn.getOperandType());
}
@Override
public void visit(CastNumberInstruction insn) {
type(insn.getReceiver(), insn.getTargetType());
}
@Override
public void visit(BinaryInstruction insn) {
type(insn.getReceiver(), insn.getOperandType());
}
@Override
public void visit(CastIntegerInstruction insn) {
switch (insn.getTargetType()) {
case BYTE:
type(insn.getReceiver(), ValueType.BYTE);
break;
case CHAR:
type(insn.getReceiver(), ValueType.CHARACTER);
break;
case SHORT:
type(insn.getReceiver(), ValueType.SHORT);
break;
}
}
@Override
public void visit(ArrayLengthInstruction insn) {
type(insn.getReceiver(), ValueType.INTEGER);
}
@Override
public void visit(CloneArrayInstruction insn) {
graphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(BoundCheckInstruction insn) {
type(insn.getReceiver(), ValueType.INTEGER);
}
@Override
public void visit(InvokeInstruction insn) {
type(insn.getReceiver(), insn.getMethod().getReturnType());
}
@Override
public void visit(InvokeDynamicInstruction insn) {
type(insn.getReceiver(), insn.getMethod().getResultType());
}
@Override
public void visit(GetFieldInstruction insn) {
type(insn.getReceiver(), insn.getFieldType());
}
@Override
public void visit(UnwrapArrayInstruction insn) {
graphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(GetElementInstruction insn) {
arrayGraphBuilder.addEdge(insn.getArray().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(AssignInstruction insn) {
graphBuilder.addEdge(insn.getAssignee().getIndex(), insn.getReceiver().getIndex());
}
@Override
public void visit(NullCheckInstruction insn) {
graphBuilder.addEdge(insn.getValue().getIndex(), insn.getReceiver().getIndex());
}
void type(Variable target, NumericOperandType type) {
switch (type) {
case INT:
type(target, ValueType.INTEGER);
break;
case LONG:
type(target, ValueType.LONG);
break;
case FLOAT:
type(target, ValueType.FLOAT);
break;
case DOUBLE:
type(target, ValueType.DOUBLE);
break;
}
}
void type(Variable target, ValueType type) {
if (target != null) {
var t = mapType(type);
if (t != null) {
types[target.getIndex()] = t;
}
}
}
}
}

View File

@ -111,7 +111,7 @@ public class ValueEmitter {
String className = ((ValueType.Object) type).getClassName();
PutFieldInstruction insn = new PutFieldInstruction();
insn.setField(new FieldReference(className, name));
insn.setFieldType(type);
insn.setFieldType(value.type);
insn.setInstance(variable);
insn.setValue(value.getVariable());
pe.addInstruction(insn);

View File

@ -15,24 +15,12 @@
*/
package org.teavm.model.optimization;
import com.carrotsearch.hppc.IntHashSet;
import com.carrotsearch.hppc.IntStack;
import java.util.Arrays;
import org.teavm.model.MethodDescriptor;
import java.util.Objects;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.analysis.BaseTypeInference;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
public class SystemArrayCopyOptimization implements MethodOptimization {
private static final MethodReference ARRAY_COPY_METHOD = new MethodReference(System.class,
@ -51,7 +39,7 @@ public class SystemArrayCopyOptimization implements MethodOptimization {
var method = invoke.getMethod();
if (method.equals(ARRAY_COPY_METHOD)) {
if (typeInference == null) {
typeInference = new TypeInference(program, context.getMethod().getDescriptor());
typeInference = new TypeInference(program, context.getMethod().getReference());
}
var sourceType = typeInference.typeOf(invoke.getArguments().get(0));
var destType = typeInference.typeOf(invoke.getArguments().get(2));
@ -69,182 +57,32 @@ public class SystemArrayCopyOptimization implements MethodOptimization {
return somethingChanged;
}
private static class TypeInference {
private ValueType[] types;
private int[] assignments;
private int[] elementAssignments;
private int[][] phis;
private boolean[] present;
private boolean[] calculating;
private static class TypeInference extends BaseTypeInference<ValueType> {
TypeInference(Program program, MethodReference reference) {
super(program, reference);
}
TypeInference(Program program, MethodDescriptor descriptor) {
types = new ValueType[program.variableCount()];
assignments = new int[program.variableCount()];
elementAssignments = new int[program.variableCount()];
phis = new int[program.variableCount()][];
Arrays.fill(assignments, -1);
Arrays.fill(elementAssignments, -1);
present = new boolean[program.variableCount()];
calculating = new boolean[program.variableCount()];
var visitor = new InitialTypeVisitor(types, assignments, elementAssignments);
var params = Math.min(descriptor.parameterCount(), program.variableCount() - 1);
for (var i = 0; i < params; ++i) {
visitor.type(program.variableAt(i + 1), descriptor.parameterType(i));
@Override
public ValueType merge(ValueType a, ValueType b) {
if (!Objects.equals(a, b)) {
return null;
}
for (var block : program.getBasicBlocks()) {
for (var insn : block) {
insn.acceptVisitor(visitor);
}
for (var phi : block.getPhis()) {
var sourceIndexes = new IntHashSet();
for (var incoming : phi.getIncomings()) {
sourceIndexes.add(incoming.getValue().getIndex());
}
var inputs = sourceIndexes.toArray();
Arrays.sort(inputs);
phis[phi.getReceiver().getIndex()] = inputs;
}
}
for (int i = 0; i < types.length; ++i) {
if (types[i] != null) {
present[i] = true;
}
}
}
ValueType typeOf(Variable variable) {
if (!present[variable.getIndex()]) {
calculate(variable.getIndex());
}
return types[variable.getIndex()];
}
private void calculate(int initialVariable) {
var stack = new IntStack();
stack.push(initialVariable);
while (!stack.isEmpty()) {
var variable = stack.pop();
if (calculating[variable]) {
calculating[variable] = false;
present[variable] = true;
var inputs = phis[variable];
ValueType type;
if (inputs != null) {
type = null;
var initialized = false;
for (var input : inputs) {
if (calculating[input]) {
continue;
}
if (initialized) {
if (!type.equals(types[input])) {
type = null;
break;
}
} else {
type = types[input];
}
}
} else if (assignments[variable] >= 0) {
type = types[assignments[variable]];
} else if (elementAssignments[variable] >= 0) {
type = types[elementAssignments[variable]];
type = type instanceof ValueType.Array ? ((ValueType.Array) type).getItemType() : null;
} else {
type = null;
}
types[variable] = type;
} else {
calculating[variable] = true;
stack.push(variable);
var inputs = phis[variable];
if (inputs != null) {
for (var input : inputs) {
if (!calculating[input] && !present[input]) {
stack.push(input);
}
}
}
var assign = assignments[variable];
if (assign >= 0 && !present[assign]) {
stack.push(assign);
}
var elemAssign = elementAssignments[variable];
if (elemAssign >= 0 && !present[elemAssign]) {
stack.push(elemAssign);
}
}
}
}
}
private static class InitialTypeVisitor extends AbstractInstructionVisitor {
private ValueType[] types;
private int[] assignments;
private int[] elementAssignments;
InitialTypeVisitor(ValueType[] types, int[] assignments, int[] elementAssignments) {
this.types = types;
this.assignments = assignments;
this.elementAssignments = elementAssignments;
return a;
}
@Override
public void visit(ConstructArrayInstruction insn) {
types[insn.getReceiver().getIndex()] = insn.getItemType();
super.visit(insn);
public ValueType elementType(ValueType valueType) {
return valueType instanceof ValueType.Array ? ((ValueType.Array) valueType).getItemType() : null;
}
@Override
public void visit(ConstructMultiArrayInstruction insn) {
var type = insn.getItemType();
for (var i = 1; i < insn.getDimensions().size(); ++i) {
type = ValueType.arrayOf(type);
}
types[insn.getReceiver().getIndex()] = insn.getItemType();
public ValueType nullType() {
return null;
}
@Override
public void visit(CastInstruction insn) {
type(insn.getReceiver(), insn.getTargetType());
}
@Override
public void visit(InvokeInstruction insn) {
type(insn.getReceiver(), insn.getMethod().getReturnType());
}
@Override
public void visit(GetFieldInstruction insn) {
type(insn.getReceiver(), insn.getFieldType());
}
void type(Variable target, ValueType type) {
if (target != null && type instanceof ValueType.Array) {
types[target.getIndex()] = ((ValueType.Array) type).getItemType();
}
}
@Override
public void visit(UnwrapArrayInstruction insn) {
assignments[insn.getReceiver().getIndex()] = insn.getArray().getIndex();
}
@Override
public void visit(GetElementInstruction insn) {
elementAssignments[insn.getReceiver().getIndex()] = insn.getArray().getIndex();
}
@Override
public void visit(AssignInstruction insn) {
assignments[insn.getReceiver().getIndex()] = insn.getAssignee().getIndex();
}
@Override
public void visit(NullCheckInstruction insn) {
assignments[insn.getReceiver().getIndex()] = insn.getValue().getIndex();
public ValueType mapType(ValueType type) {
return type instanceof ValueType.Array ? ((ValueType.Array) type).getItemType() : null;
}
}
}

View File

@ -27,7 +27,7 @@ function $rt_compare(a, b) {
return a > b ? 1 : a < b ? -1 : a === b ? 0 : 1;
}
function $rt_isInstance(obj, cls) {
return obj !== null && !!obj.constructor.$meta && $rt_isAssignable(obj.constructor, cls);
return obj instanceof $rt_objcls() && !!obj.constructor.$meta && $rt_isAssignable(obj.constructor, cls);
}
function $rt_isAssignable(from, to) {
if (from === to) {

View File

@ -0,0 +1,26 @@
/*
* 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.jso.core;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
public abstract class JSFinalizationRegistry implements JSObject {
public abstract void register(JSObject obj, JSObject token);
@JSBody(params = "consumer", script = "return new FinalizationRegistry(consumer);")
public static native JSFinalizationRegistry create(JSFinalizationRegistryConsumer consumer);
}

View File

@ -0,0 +1,24 @@
/*
* 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.jso.core;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
@JSFunctor
public interface JSFinalizationRegistryConsumer extends JSObject {
void accept(JSObject obj);
}

View File

@ -0,0 +1,36 @@
/*
* 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.jso.core;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
public abstract class JSMap<K extends JSObject, V extends JSObject> implements JSObject {
public abstract V get(K key);
public abstract boolean has(K key);
public abstract JSMap<K, V> set(K key, V value);
public abstract boolean delete(K key);
public abstract void clear();
@JSBody(script = "return new Map();")
@NoSideEffects
public static native <K extends JSObject, V extends JSObject> JSMap<K, V> create();
}

View File

@ -61,4 +61,8 @@ public final class JSObjects {
@JSBody(params = { "object", "name" }, script = "return name in object;")
@NoSideEffects
public static native boolean hasProperty(JSObject object, String name);
@JSBody(params = "object", script = "return Object.getPrototypeOf(object);")
@NoSideEffects
public static native JSObject getPrototypeOf(JSObject object);
}

View File

@ -0,0 +1,32 @@
/*
* 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.jso.core;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
public abstract class JSSymbol<T extends JSObject> implements JSObject {
@JSBody(params = "name", script = "return Symbol(name);")
public static native <T extends JSObject> JSSymbol<T> create(String name);
@JSBody(params = "obj", script = "return obj[this];")
public native T get(JSObject obj);
@JSBody(params = { "obj", "value" }, script = "obj[this] = value;")
@NoSideEffects
public native void set(JSObject obj, T value);
}

View File

@ -0,0 +1,34 @@
/*
* 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.jso.core;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
public abstract class JSWeakMap<K extends JSObject, V extends JSObject> implements JSObject {
public abstract V get(K key);
public abstract boolean has(K key);
public abstract JSWeakMap<K, V> set(K key, V value);
public abstract boolean remove(K key);
@JSBody(script = "return new WeakMap();")
@NoSideEffects
public static native <K extends JSObject, V extends JSObject> JSWeakMap<K, V> create();
}

View File

@ -0,0 +1,32 @@
/*
* 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.jso.core;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
public abstract class JSWeakRef<T extends JSObject> implements JSObject {
public abstract T deref();
@JSBody(params = "value", script = "return new WeakRef(value);")
@NoSideEffects
public static native <T extends JSObject> JSWeakRef<T> create(T value);
@JSBody(script = "return typeof WeakRef !== 'undefined';")
@NoSideEffects
public static native boolean isSupported();
}

View File

@ -16,7 +16,6 @@
package org.teavm.jso.impl;
import java.lang.reflect.Array;
import java.util.function.Function;
import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.dependency.PluggableDependency;
@ -148,11 +147,11 @@ final class JS {
return result;
}
public static <T extends JSObject> Function<T[], JSArray<T>> arrayWrapper() {
public static <T extends JSObject> WrapFunction<T[], JSArray<T>> arrayWrapper() {
return JS::wrap;
}
public static <T extends JSObject, S> JSArray<T> map(S[] array, Function<S, T> f) {
public static <T extends JSObject, S> JSArray<T> map(S[] array, WrapFunction<S, T> f) {
if (array == null) {
return null;
}
@ -163,7 +162,15 @@ final class JS {
return result;
}
public static <T extends JSObject, S> Function<S[], JSArray<T>> arrayMapper(Function<S, T> f) {
public interface WrapFunction<S, T extends JSObject> {
T apply(S obj);
}
public interface UnwrapFunction<S extends JSObject, T> {
T apply(S obj);
}
public static <T extends JSObject, S> WrapFunction<S[], JSArray<T>> arrayMapper(WrapFunction<S, T> f) {
return array -> map(array, f);
}
@ -178,7 +185,7 @@ final class JS {
return result;
}
public static Function<boolean[], JSArray<JSBoolean>> booleanArrayWrapper() {
public static WrapFunction<boolean[], JSArray<JSBoolean>> booleanArrayWrapper() {
return JS::wrap;
}
@ -193,7 +200,7 @@ final class JS {
return result;
}
public static Function<byte[], JSArray<JSNumber>> byteArrayWrapper() {
public static WrapFunction<byte[], JSArray<JSNumber>> byteArrayWrapper() {
return JS::wrap;
}
@ -208,7 +215,7 @@ final class JS {
return result;
}
public static Function<short[], JSArray<JSNumber>> shortArrayWrapper() {
public static WrapFunction<short[], JSArray<JSNumber>> shortArrayWrapper() {
return JS::wrap;
}
@ -223,7 +230,7 @@ final class JS {
return result;
}
public static Function<char[], JSArray<JSNumber>> charArrayWrapper() {
public static WrapFunction<char[], JSArray<JSNumber>> charArrayWrapper() {
return JS::wrap;
}
@ -238,7 +245,7 @@ final class JS {
return result;
}
public static Function<int[], JSArray<JSNumber>> intArrayWrapper() {
public static WrapFunction<int[], JSArray<JSNumber>> intArrayWrapper() {
return JS::wrap;
}
@ -253,7 +260,7 @@ final class JS {
return result;
}
public static Function<String[], JSArray<JSString>> stringArrayWrapper() {
public static WrapFunction<String[], JSArray<JSString>> stringArrayWrapper() {
return JS::wrap;
}
@ -268,7 +275,7 @@ final class JS {
return result;
}
public static Function<float[], JSArray<JSNumber>> floatArrayWrapper() {
public static WrapFunction<float[], JSArray<JSNumber>> floatArrayWrapper() {
return JS::wrap;
}
@ -283,7 +290,7 @@ final class JS {
return result;
}
public static Function<double[], JSArray<JSNumber>> doubleArrayWrapper() {
public static WrapFunction<double[], JSArray<JSNumber>> doubleArrayWrapper() {
return JS::wrap;
}
@ -299,11 +306,12 @@ final class JS {
return result;
}
public static <T extends JSObject> Function<JSArrayReader<T>, T[]> arrayUnwrapper(Class<T> type) {
public static <T extends JSObject> UnwrapFunction<JSArrayReader<T>, T[]> arrayUnwrapper(Class<T> type) {
return array -> unwrapArray(type, array);
}
public static <S extends JSObject, T> T[] unmapArray(Class<T> type, JSArrayReader<S> array, Function<S, T> f) {
public static <S extends JSObject, T> T[] unmapArray(Class<T> type, JSArrayReader<S> array,
UnwrapFunction<S, T> f) {
if (array == null) {
return null;
}
@ -315,7 +323,8 @@ final class JS {
return result;
}
public static <T, S extends JSObject> Function<JSArray<S>, T[]> arrayUnmapper(Class<T> type, Function<S, T> f) {
public static <T, S extends JSObject> UnwrapFunction<JSArray<S>, T[]> arrayUnmapper(Class<T> type,
UnwrapFunction<S, T> f) {
return array -> unmapArray(type, array, f);
}
@ -330,7 +339,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSBoolean>, boolean[]> booleanArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSBoolean>, boolean[]> booleanArrayUnwrapper() {
return JS::unwrapBooleanArray;
}
@ -345,7 +354,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, byte[]> byteArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, byte[]> byteArrayUnwrapper() {
return JS::unwrapByteArray;
}
@ -360,7 +369,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, short[]> shortArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, short[]> shortArrayUnwrapper() {
return JS::unwrapShortArray;
}
@ -375,7 +384,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, int[]> intArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, int[]> intArrayUnwrapper() {
return JS::unwrapIntArray;
}
@ -390,7 +399,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, char[]> charArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, char[]> charArrayUnwrapper() {
return JS::unwrapCharArray;
}
@ -405,7 +414,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, float[]> floatArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, float[]> floatArrayUnwrapper() {
return JS::unwrapFloatArray;
}
@ -420,7 +429,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSNumber>, double[]> doubleArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSNumber>, double[]> doubleArrayUnwrapper() {
return JS::unwrapDoubleArray;
}
@ -435,7 +444,7 @@ final class JS {
return result;
}
public static Function<JSArrayReader<JSString>, String[]> stringArrayUnwrapper() {
public static UnwrapFunction<JSArrayReader<JSString>, String[]> stringArrayUnwrapper() {
return JS::unwrapStringArray;
}

View File

@ -58,21 +58,40 @@ import org.teavm.model.Program;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.IsInstanceInstruction;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.ModelUtils;
import org.teavm.model.util.ProgramUtils;
class JSClassProcessor {
private static final String NO_SIDE_EFFECTS = NoSideEffects.class.getName();
private static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", Object.class,
Object.class);
private static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class,
Object.class);
private static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class,
JSObject.class);
private static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap",
Object.class, JSObject.class);
private static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs",
Object.class, boolean.class);
private final ClassReaderSource classSource;
private final JSBodyRepository repository;
private final JavaInvocationProcessor javaInvocationProcessor;
private Program program;
private JSTypeInference types;
private final List<Instruction> replacement = new ArrayList<>();
private final JSTypeHelper typeHelper;
private final Diagnostics diagnostics;
@ -206,37 +225,157 @@ class JSClassProcessor {
void processProgram(MethodHolder methodToProcess) {
setCurrentProgram(methodToProcess.getProgram());
types = new JSTypeInference(typeHelper, program, methodToProcess.getReference());
types.ensure();
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) {
var block = program.basicBlockAt(i);
for (var insn : block) {
if (insn instanceof CastInstruction) {
replacement.clear();
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
if (processCast((CastInstruction) insn, callLocation)) {
insn.insertNextAll(replacement);
insn.delete();
}
} else if (insn instanceof IsInstanceInstruction) {
processIsInstance((IsInstanceInstruction) insn);
} else if (insn instanceof InvokeInstruction) {
InvokeInstruction invoke = (InvokeInstruction) insn;
var invoke = (InvokeInstruction) insn;
processInvokeArgs(invoke);
MethodReader method = getMethod(invoke.getMethod().getClassName(),
invoke.getMethod().getDescriptor());
var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor());
if (method == null) {
continue;
}
CallLocation callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
replacement.clear();
if (processInvocation(method, callLocation, invoke, methodToProcess)) {
insn.insertNextAll(replacement);
insn.delete();
}
} else if (insn instanceof PutFieldInstruction) {
processPutField((PutFieldInstruction) insn);
} else if (insn instanceof GetElementInstruction) {
processGetFromArray((GetElementInstruction) insn);
} else if (insn instanceof PutElementInstruction) {
processPutIntoArray((PutElementInstruction) insn);
} else if (insn instanceof ConstructArrayInstruction) {
processConstructArray((ConstructArrayInstruction) insn);
} else if (insn instanceof ExitInstruction) {
var exit = (ExitInstruction) insn;
exit.setValueToReturn(wrapJsAsJava(insn, exit.getValueToReturn(),
methodToProcess.getResultType()));
} else if (insn instanceof ClassConstantInstruction) {
processClassConstant((ClassConstantInstruction) insn);
}
}
}
}
private void processInvokeArgs(InvokeInstruction invoke) {
if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
return;
}
Variable[] newArgs = null;
for (var i = 0; i < invoke.getArguments().size(); ++i) {
var type = invoke.getMethod().parameterType(i);
var arg = invoke.getArguments().get(i);
var newArg = wrapJsAsJava(invoke, arg, type);
if (newArg != arg) {
if (newArgs == null) {
newArgs = invoke.getArguments().toArray(new Variable[0]);
}
newArgs[i] = newArg;
}
}
if (newArgs != null) {
invoke.setArguments(newArgs);
}
if (invoke.getInstance() != null) {
invoke.setInstance(wrapJsAsJava(invoke, invoke.getInstance(),
ValueType.object(invoke.getMethod().getClassName())));
}
}
private void processPutField(PutFieldInstruction putField) {
putField.setValue(wrapJsAsJava(putField, putField.getValue(), putField.getFieldType()));
}
private void processGetFromArray(GetElementInstruction insn) {
if (insn.getType() != ArrayElementType.OBJECT) {
return;
}
var type = types.typeOf(insn.getReceiver());
if (type == JSType.JS || type == JSType.MIXED) {
var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL);
unwrap.setMethod(type == JSType.MIXED ? MAYBE_UNWRAP : UNWRAP);
unwrap.setArguments(program.createVariable());
unwrap.setReceiver(insn.getReceiver());
unwrap.setLocation(insn.getLocation());
insn.setReceiver(unwrap.getArguments().get(0));
insn.insertNext(unwrap);
}
}
private void processPutIntoArray(PutElementInstruction insn) {
if (insn.getType() != ArrayElementType.OBJECT) {
return;
}
var type = types.typeOf(insn.getValue());
if (type == JSType.JS || type == JSType.MIXED) {
var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL);
wrap.setMethod(type == JSType.MIXED ? MAYBE_WRAP : WRAP);
wrap.setArguments(insn.getValue());
wrap.setReceiver(program.createVariable());
wrap.setLocation(insn.getLocation());
insn.setValue(wrap.getReceiver());
insn.insertPrevious(wrap);
}
}
private void processConstructArray(ConstructArrayInstruction insn) {
insn.setItemType(processType(insn.getItemType()));
}
private void processClassConstant(ClassConstantInstruction insn) {
insn.setConstant(processType(insn.getConstant()));
}
private ValueType processType(ValueType type) {
return processType(typeHelper, type);
}
static ValueType processType(JSTypeHelper typeHelper, ValueType type) {
var originalType = type;
var degree = 0;
while (type instanceof ValueType.Array) {
degree++;
type = ((ValueType.Array) type).getItemType();
}
if (!(type instanceof ValueType.Object)) {
return originalType;
}
var className = ((ValueType.Object) type).getClassName();
if (!typeHelper.isJavaScriptClass(className)) {
return originalType;
}
type = ValueType.object(JSWrapper.class.getName());
while (degree-- > 0) {
type = ValueType.arrayOf(type);
}
return type;
}
private boolean processCast(CastInstruction cast, CallLocation location) {
if (!(cast.getTargetType() instanceof ValueType.Object)) {
cast.setTargetType(processType(cast.getTargetType()));
return false;
}
@ -244,6 +383,9 @@ class JSClassProcessor {
if (!typeHelper.isJavaScriptClass(targetClassName)) {
return false;
}
cast.setValue(unwrapJavaToJs(cast, cast.getValue()));
ClassReader targetClass = classSource.get(targetClassName);
if (targetClass.getAnnotations().get(JSFunctor.class.getName()) == null) {
AssignInstruction assign = new AssignInstruction();
@ -255,7 +397,7 @@ class JSClassProcessor {
}
Variable result = marshaller.unwrapFunctor(location, cast.getValue(), targetClass);
AssignInstruction assign = new AssignInstruction();
var assign = new AssignInstruction();
assign.setLocation(location.getSourceLocation());
assign.setAssignee(result);
assign.setReceiver(cast.getReceiver());
@ -264,6 +406,75 @@ class JSClassProcessor {
return true;
}
private void processIsInstance(IsInstanceInstruction isInstance) {
if (!(isInstance.getType() instanceof ValueType.Object)) {
isInstance.setType(processType(isInstance.getType()));
return;
}
String targetClassName = ((ValueType.Object) isInstance.getType()).getClassName();
if (!typeHelper.isJavaScriptClass(targetClassName)) {
return;
}
var type = types.typeOf(isInstance.getValue());
if (type == JSType.JS) {
var replacement = new IntegerConstantInstruction();
replacement.setConstant(1);
replacement.setReceiver(isInstance.getReceiver());
replacement.setLocation(isInstance.getLocation());
isInstance.replace(replacement);
return;
}
var replacement = new InvokeInstruction();
replacement.setType(InvocationType.SPECIAL);
replacement.setMethod(IS_JS);
replacement.setArguments(isInstance.getValue());
replacement.setReceiver(isInstance.getReceiver());
replacement.setLocation(isInstance.getLocation());
isInstance.replace(replacement);
}
private Variable wrapJsAsJava(Instruction instruction, Variable var, ValueType type) {
if (!(type instanceof ValueType.Object)) {
return var;
}
var cls = ((ValueType.Object) type).getClassName();
if (typeHelper.isJavaScriptClass(cls)) {
return var;
}
var varType = types.typeOf(var);
if (varType != JSType.JS && varType != JSType.MIXED) {
return var;
}
var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL);
wrap.setMethod(varType == JSType.JS ? WRAP : MAYBE_WRAP);
wrap.setArguments(var);
wrap.setReceiver(program.createVariable());
wrap.setLocation(instruction.getLocation());
instruction.insertPrevious(wrap);
return wrap.getReceiver();
}
private Variable unwrapJavaToJs(Instruction instruction, Variable var) {
var varType = types.typeOf(var);
if (varType != JSType.JAVA && varType != JSType.MIXED) {
return var;
}
var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL);
unwrap.setMethod(varType == JSType.JAVA ? UNWRAP : MAYBE_UNWRAP);
unwrap.setArguments(var);
unwrap.setReceiver(program.createVariable());
unwrap.setLocation(instruction.getLocation());
instruction.insertPrevious(unwrap);
return unwrap.getReceiver();
}
private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke,
MethodHolder methodToProcess) {
if (method.getAnnotations().get(JSBody.class.getName()) != null) {

View File

@ -16,7 +16,6 @@
package org.teavm.jso.impl;
import java.util.Arrays;
import java.util.function.Function;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSArrayReader;
@ -37,29 +36,29 @@ final class JSMethods {
public static final MethodReference ARRAY_DATA = new MethodReference(JS.class, "arrayData",
Object.class, JSObject.class);
public static final MethodReference ARRAY_MAPPER = new MethodReference(JS.class, "arrayMapper",
Function.class, Function.class);
JS.WrapFunction.class, JS.WrapFunction.class);
public static final MethodReference BOOLEAN_ARRAY_WRAPPER = new MethodReference(JS.class, "booleanArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference BYTE_ARRAY_WRAPPER = new MethodReference(JS.class, "byteArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference SHORT_ARRAY_WRAPPER = new MethodReference(JS.class, "shortArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference CHAR_ARRAY_WRAPPER = new MethodReference(JS.class, "charArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference INT_ARRAY_WRAPPER = new MethodReference(JS.class, "intArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference FLOAT_ARRAY_WRAPPER = new MethodReference(JS.class, "floatArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference DOUBLE_ARRAY_WRAPPER = new MethodReference(JS.class, "doubleArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference STRING_ARRAY_WRAPPER = new MethodReference(JS.class, "stringArrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference ARRAY_WRAPPER = new MethodReference(JS.class, "arrayWrapper",
Function.class);
JS.WrapFunction.class);
public static final MethodReference ARRAY_UNMAPPER = new MethodReference(JS.class, "arrayUnmapper",
Class.class, Function.class, Function.class);
Class.class, JS.UnwrapFunction.class, JS.UnwrapFunction.class);
public static final MethodReference UNMAP_ARRAY = new MethodReference(JS.class, "unmapArray", Class.class,
JSArrayReader.class, Function.class, Object[].class);
JSArrayReader.class, JS.UnwrapFunction.class, Object[].class);
public static final MethodReference UNWRAP_BOOLEAN_ARRAY = new MethodReference(JS.class, "unwrapBooleanArray",
JSArrayReader.class, boolean[].class);
public static final MethodReference UNWRAP_BYTE_ARRAY = new MethodReference(JS.class, "unwrapByteArray",
@ -79,23 +78,23 @@ final class JSMethods {
public static final MethodReference UNWRAP_ARRAY = new MethodReference(JS.class, "unwrapArray", Class.class,
JSArrayReader.class, JSObject[].class);
public static final MethodReference BOOLEAN_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"booleanArrayUnwrapper", Function.class);
"booleanArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference BYTE_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"byteArrayUnwrapper", Function.class);
"byteArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference SHORT_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"shortArrayUnwrapper", Function.class);
"shortArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference CHAR_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"charArrayUnwrapper", Function.class);
"charArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference INT_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"intArrayUnwrapper", Function.class);
"intArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference FLOAT_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"floatArrayUnwrapper", Function.class);
"floatArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference DOUBLE_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"doubleArrayUnwrapper", Function.class);
"doubleArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference STRING_ARRAY_UNWRAPPER = new MethodReference(JS.class,
"stringArrayUnwrapper", Function.class);
"stringArrayUnwrapper", JS.UnwrapFunction.class);
public static final MethodReference ARRAY_UNWRAPPER = new MethodReference(JS.class,
"arrayUnwrapper", Class.class, Function.class);
"arrayUnwrapper", Class.class, JS.UnwrapFunction.class);
public static final MethodReference DATA_TO_BYTE_ARRAY = new MethodReference(JS.class,
"dataToByteArray", JSObject.class, byte[].class);

View File

@ -52,6 +52,18 @@ public class JSOPlugin implements TeaVMPlugin {
jsHost.add(new MethodReference(JSExceptions.class, "getJSException", Throwable.class, JSObject.class),
exceptionsGenerator);
var wrapperGenerator = new JSWrapperGenerator();
jsHost.add(new MethodReference(JSWrapper.class, "directJavaToJs", Object.class, JSObject.class),
wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class),
wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "wrapperToJs", JSWrapper.class, JSObject.class),
wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
wrapperGenerator);
host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
wrapperGenerator);
TeaVMPluginUtil.handleNatives(host, JS.class);
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.jso.impl;
import java.util.Objects;
public class JSType {
private JSType() {
}
public static final JSType NULL = new JSType();
public static final JSType JS = new JSType();
public static final JSType JAVA = new JSType();
public static final JSType MIXED = new JSType();
public static final class ArrayType extends JSType {
public final JSType elementType;
private ArrayType(JSType elementType) {
this.elementType = elementType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ArrayType arrayType = (ArrayType) o;
return Objects.equals(elementType, arrayType.elementType);
}
@Override
public int hashCode() {
return Objects.hash(elementType);
}
}
public static JSType arrayOf(JSType elementType) {
return new ArrayType(elementType);
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.jso.impl;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.analysis.BaseTypeInference;
public class JSTypeInference extends BaseTypeInference<JSType> {
private JSTypeHelper typeHelper;
public JSTypeInference(JSTypeHelper typeHelper, Program program, MethodReference reference) {
super(program, reference);
this.typeHelper = typeHelper;
}
@Override
protected JSType mapType(ValueType type) {
if (type instanceof ValueType.Object) {
var className = ((ValueType.Object) type).getClassName();
if (typeHelper.isJavaScriptClass(className)) {
return JSType.JS;
}
} else if (type instanceof ValueType.Array) {
var elementType = mapType(((ValueType.Array) type).getItemType());
return JSType.arrayOf(elementType);
}
return JSType.JAVA;
}
@Override
protected JSType nullType() {
return JSType.NULL;
}
@Override
protected JSType merge(JSType a, JSType b) {
if (a == JSType.NULL) {
return b;
} else if (b == JSType.NULL) {
return a;
} else if (a == b) {
return a;
} else if (a instanceof JSType.ArrayType) {
if (b instanceof JSType.ArrayType) {
var elementType = merge(((JSType.ArrayType) a).elementType, ((JSType.ArrayType) b).elementType);
return JSType.arrayOf(elementType);
} else if (b == JSType.JAVA) {
return JSType.JAVA;
} else {
return JSType.MIXED;
}
} else if (b instanceof JSType.ArrayType) {
if (a == JSType.JAVA) {
return JSType.JAVA;
} else {
return JSType.MIXED;
}
} else {
return JSType.MIXED;
}
}
@Override
protected JSType elementType(JSType jsType) {
return jsType instanceof JSType.ArrayType ? ((JSType.ArrayType) jsType).elementType : JSType.MIXED;
}
}

View File

@ -17,7 +17,6 @@ package org.teavm.jso.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
@ -153,7 +152,7 @@ class JSValueMarshaller {
insn = new InvokeInstruction();
insn.setMethod(referenceCache.getCached(new MethodReference(JS.class.getName(), "map",
getWrappedType(type), ValueType.parse(Function.class), getWrapperType(type))));
getWrappedType(type), ValueType.parse(JS.WrapFunction.class), getWrapperType(type))));
insn.setArguments(var, function);
insn.setReceiver(result);
insn.setType(InvocationType.SPECIAL);
@ -334,8 +333,8 @@ class JSValueMarshaller {
if (insn.getMethod().parameterCount() == 2) {
Variable cls = program.createVariable();
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(type);
var clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(JSClassProcessor.processType(typeHelper, type));
clsInsn.setLocation(location.getSourceLocation());
clsInsn.setReceiver(cls);
replacement.add(clsInsn);
@ -358,8 +357,8 @@ class JSValueMarshaller {
if (insn.getMethod().parameterCount() == 1) {
Variable cls = program.createVariable();
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(type);
var clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(JSClassProcessor.processType(typeHelper, type));
clsInsn.setLocation(location.getSourceLocation());
clsInsn.setReceiver(cls);
replacement.add(clsInsn);
@ -373,8 +372,8 @@ class JSValueMarshaller {
type = ValueType.arrayOf(type);
Variable cls = program.createVariable();
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(type);
var clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(JSClassProcessor.processType(typeHelper, type));
clsInsn.setLocation(location.getSourceLocation());
clsInsn.setReceiver(cls);
replacement.add(clsInsn);
@ -389,8 +388,8 @@ class JSValueMarshaller {
}
Variable cls = program.createVariable();
ClassConstantInstruction clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(ValueType.arrayOf(type));
var clsInsn = new ClassConstantInstruction();
clsInsn.setConstant(JSClassProcessor.processType(typeHelper, ValueType.arrayOf(type)));
clsInsn.setLocation(location.getSourceLocation());
clsInsn.setReceiver(cls);
replacement.add(clsInsn);

View File

@ -0,0 +1,192 @@
/*
* 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.jso.impl;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSBoolean;
import org.teavm.jso.core.JSFinalizationRegistry;
import org.teavm.jso.core.JSMap;
import org.teavm.jso.core.JSNumber;
import org.teavm.jso.core.JSObjects;
import org.teavm.jso.core.JSString;
import org.teavm.jso.core.JSWeakMap;
import org.teavm.jso.core.JSWeakRef;
public final class JSWrapper {
private static final JSWeakMap<JSObject, JSNumber> hashCodes = JSWeakMap.create();
private static final JSWeakMap<JSObject, JSWeakRef<JSObject>> wrappers = JSWeakRef.isSupported()
? JSWeakMap.create() : null;
private static final JSMap<JSString, JSWeakRef<JSObject>> stringWrappers = JSWeakRef.isSupported()
? JSMap.create() : null;
private static final JSMap<JSNumber, JSWeakRef<JSObject>> numberWrappers = JSWeakRef.isSupported()
? JSMap.create() : null;
private static final JSFinalizationRegistry stringFinalizationRegistry;
private static final JSFinalizationRegistry numberFinalizationRegistry;
private static int hashCodeGen;
public final JSObject js;
static {
stringFinalizationRegistry = stringWrappers != null
? JSFinalizationRegistry.create(token -> stringWrappers.delete((JSString) token))
: null;
numberFinalizationRegistry = numberWrappers != null
? JSFinalizationRegistry.create(token -> numberWrappers.delete((JSNumber) token))
: null;
}
private JSWrapper(JSObject js) {
this.js = js;
}
public static Object wrap(Object o) {
if (o == null) {
return null;
}
var js = directJavaToJs(o);
if (wrappers != null) {
var type = JSObjects.typeOf(js);
if (type.equals("object") || type.equals("function")) {
var existingRef = wrappers.get(js);
var existing = !JSObjects.isUndefined(existingRef) ? existingRef.deref() : JSObjects.undefined();
if (JSObjects.isUndefined(existing)) {
var wrapper = new JSWrapper(js);
wrappers.set(js, JSWeakRef.create(wrapperToJs(wrapper)));
return wrapper;
} else {
return jsToWrapper(existing);
}
} else if (type.equals("string")) {
var jsString = (JSString) js;
var existingRef = stringWrappers.get(jsString);
var existing = !JSObjects.isUndefined(existingRef) ? existingRef.deref() : JSObjects.undefined();
if (JSObjects.isUndefined(existing)) {
var wrapper = new JSWrapper(js);
var wrapperAsJs = wrapperToJs(wrapper);
stringWrappers.set(jsString, JSWeakRef.create(wrapperAsJs));
stringFinalizationRegistry.register(wrapperAsJs, jsString);
return wrapper;
} else {
return jsToWrapper(existing);
}
} else if (type.equals("number")) {
var jsNumber = (JSNumber) js;
var existingRef = numberWrappers.get(jsNumber);
var existing = !JSObjects.isUndefined(existingRef) ? existingRef.deref() : JSObjects.undefined();
if (JSObjects.isUndefined(existing)) {
var wrapper = new JSWrapper(js);
var wrapperAsJs = wrapperToJs(wrapper);
numberWrappers.set(jsNumber, JSWeakRef.create(wrapperAsJs));
numberFinalizationRegistry.register(wrapperAsJs, jsNumber);
return wrapper;
} else {
return jsToWrapper(existing);
}
}
}
return new JSWrapper(js);
}
public static Object maybeWrap(Object o) {
return o == null || isJava(o) ? o : wrap(o);
}
@NoSideEffects
public static native JSObject directJavaToJs(Object obj);
@NoSideEffects
private static native JSObject wrapperToJs(JSWrapper obj);
@NoSideEffects
private static native JSWrapper jsToWrapper(JSObject obj);
@NoSideEffects
public static native boolean isJava(Object obj);
public static JSObject unwrap(Object o) {
if (o == null) {
return null;
}
return ((JSWrapper) o).js;
}
public static JSObject maybeUnwrap(Object o) {
if (o == null) {
return null;
}
return isJava(o) ? unwrap(o) : directJavaToJs(o);
}
public static boolean isJs(Object o) {
if (o == null) {
return false;
}
return !isJava(o) || o instanceof JSWrapper;
}
@Override
public int hashCode() {
var type = JSObjects.typeOf(js);
if (type.equals("object") || type.equals("symbol") || type.equals("function")) {
var code = hashCodes.get(js);
if (JSObjects.isUndefined(code)) {
code = JSNumber.valueOf(++hashCodeGen);
hashCodes.set(js, code);
}
return code.intValue();
} else if (type.equals("number")) {
return ((JSNumber) js).intValue();
} else if (type.equals("bigint")) {
return bigintTruncate(js);
} else if (type.equals("string")) {
var s = (JSString) js;
var hashCode = 0;
for (var i = 0; i < s.getLength(); ++i) {
hashCode = 31 * hashCode + s.charCodeAt(i);
}
return hashCode;
} else if (type.equals("boolean")) {
return js == JSBoolean.valueOf(true) ? 1 : 0;
} else {
return 0;
}
}
@JSBody(params = "bigint", script = "return BigInt.asIntN(bigint, 32);")
@NoSideEffects
private static native int bigintTruncate(JSObject bigint);
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof JSWrapper)) {
return false;
}
return js == ((JSWrapper) obj).js;
}
@Override
public String toString() {
return JSObjects.toString(js);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.jso.impl;
import java.io.IOException;
import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.InjectorContext;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.MethodReference;
public class JSWrapperGenerator implements Injector, DependencyPlugin {
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "directJavaToJs":
case "wrapperToJs":
case "jsToWrapper":
context.writeExpr(context.getArgument(0));
break;
case "isJava":
context.writeExpr(context.getArgument(0));
context.getWriter().append(" instanceof ").appendFunction("$rt_objcls").append("()");
break;
}
}
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getMethod().getName().equals("jsToWrapper")) {
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
}
}
}

View File

@ -0,0 +1,237 @@
/*
* 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.jso.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSNumber;
import org.teavm.jso.core.JSObjects;
import org.teavm.jso.core.JSString;
import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.WholeClassCompilation;
@RunWith(TeaVMTestRunner.class)
@WholeClassCompilation
@SkipJVM
public class JSWrapperTest {
private List<Object> list = new ArrayList<>();
private Object field1;
@Test
public void simple() {
list.add(JSNumber.valueOf(23));
list.add(JSString.valueOf("q"));
list.add(JSString.valueOf("q"));
list.add("q");
list.add("w");
assertEquals("23", list.get(0).toString());
assertEquals("q", list.get(1).toString());
assertEquals(list.get(1), list.get(2));
assertNotEquals(list.get(0), list.get(2));
assertNotEquals(list.get(1), list.get(3));
assertEquals(23, ((JSNumber) list.get(0)).intValue());
assertEquals("q", ((JSString) list.get(1)).stringValue());
try {
assertEquals("q", ((JSString) list.get(3)).stringValue());
fail("Expected exception not thrown");
} catch (ClassCastException e) {
// ok
}
}
@Test
public void testHashCode() {
list.add(JSNumber.valueOf(23));
var o1 = JSObjects.create();
list.add(o1);
list.add(o1);
var o2 = JSObjects.create();
list.add(o2);
assertEquals(23, list.get(0).hashCode());
assertEquals(list.get(1).hashCode(), list.get(2).hashCode());
assertNotEquals(list.get(1).hashCode(), list.get(3).hashCode());
}
@Test
public void referentialEquality() {
var o1 = JSObjects.create();
list.add(o1);
list.add(o1);
var o2 = JSObjects.create();
list.add(o2);
assertSame(list.get(0), list.get(1));
assertNotSame(list.get(0), list.get(2));
assertSame(JSString.valueOf("q"), JSString.valueOf("q"));
assertSame(JSNumber.valueOf(23), JSNumber.valueOf(23));
}
@Test
public void equality() {
var o1 = JSObjects.create();
list.add(o1);
list.add(o1);
var o2 = JSObjects.create();
list.add(o2);
list.add(JSString.valueOf("q"));
list.add(JSString.valueOf("q"));
list.add(JSString.valueOf("w"));
assertEquals(list.get(0), list.get(1));
assertNotEquals(list.get(0), list.get(2));
assertEquals(list.get(3), list.get(4));
assertNotEquals(list.get(3), list.get(5));
}
@Test
public void wrapNull() {
list.add(jsNull());
assertEquals("null", Objects.toString(list.get(0)));
try {
list.get(0).toString();
fail("Expected exception not thrown");
} catch (NullPointerException e) {
// ok
}
}
@Test
public void unwrapNull() {
list.add(null);
assertTrue(isNull((JSObject) list.get(0)));
}
@Test
public void instanceOf() {
list.add(23);
list.add(JSNumber.valueOf(23));
list.add(null);
list.add(jsNull());
assertTrue(list.get(0) instanceof Object);
assertTrue(list.get(0) instanceof Integer);
assertFalse(list.get(0) instanceof JSNumber);
assertTrue(list.get(1) instanceof Object);
assertFalse(list.get(1) instanceof Integer);
assertTrue(list.get(1) instanceof JSNumber);
assertFalse(list.get(2) instanceof Object);
assertFalse(list.get(2) instanceof Integer);
assertFalse(list.get(2) instanceof JSNumber);
assertTrue(JSObjects.create() instanceof JSObject);
}
@Test
public void mergeTypes() {
for (var i = 0; i < 2; ++i) {
var o = i == 0 ? JSNumber.valueOf(23) : 23;
list.add(o);
}
assertTrue(list.get(0) instanceof JSNumber);
assertFalse(list.get(0) instanceof Integer);
assertFalse(list.get(1) instanceof JSNumber);
assertTrue(list.get(1) instanceof Integer);
}
@Test
public void testClass() {
list.add(23);
list.add(JSNumber.valueOf(23));
assertEquals(Integer.class, list.get(0).getClass());
assertEquals(JSNumber.class, list.get(1).getClass());
assertEquals("java.lang.Integer", list.get(0).getClass().getName());
assertEquals("org.teavm.jso.impl.JSWrapper", list.get(1).getClass().getName());
}
@Test
public void array() {
var array = new JSString[3];
array[0] = JSString.valueOf("q");
array[1] = JSString.valueOf("q");
array[2] = JSString.valueOf("w");
assertEquals(JSString.valueOf("q"), array[0]);
assertEquals(JSString.valueOf("w"), array[2]);
assertEquals("q", array[0].stringValue());
assertEquals("w", array[2].stringValue());
assertEquals(array[0], array[1]);
assertEquals(JSString[].class, array.getClass());
assertEquals(JSString.class, array.getClass().getComponentType());
}
@Test
public void objectArray() {
var array = new Object[4];
array[0] = JSString.valueOf("q");
array[1] = JSString.valueOf("q");
array[2] = JSString.valueOf("w");
array[3] = "q";
assertEquals(JSString.valueOf("q"), array[0]);
assertEquals(JSString.valueOf("w"), array[2]);
assertEquals("q", array[3]);
assertEquals("q", ((JSString) array[0]).stringValue());
assertEquals(array[0], array[1]);
assertNotEquals(array[0], array[2]);
assertNotEquals(array[0], array[3]);
}
@Test
public void field() {
field1 = 23;
assertEquals("java.lang.Integer", field1.getClass().getName());
field1 = JSNumber.valueOf(23);
assertEquals("org.teavm.jso.impl.JSWrapper", field1.getClass().getName());
}
@JSBody(script = "return null;")
private static native JSObject jsNull();
@JSBody(params = "o", script = "return o === null;")
private static native boolean isNull(JSObject o);
}