JS: Support JSByRef annotation on method return types

This commit is contained in:
Alexey Andreev 2019-03-21 18:09:38 +03:00
parent f664d94d74
commit ac627580c6
8 changed files with 172 additions and 10 deletions

View File

@ -26,6 +26,6 @@ import java.lang.annotation.Target;
* to: byte[], short[], char[], int[], float[], double[] or T[], where T is JSObject.</p> * to: byte[], short[], char[], int[], float[], double[] or T[], where T is JSObject.</p>
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) @Target({ ElementType.PARAMETER, ElementType.METHOD })
public @interface JSByRef { public @interface JSByRef {
} }

View File

@ -35,6 +35,34 @@ final class JS {
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject arrayData(Object array); public static native JSObject arrayData(Object array);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native byte[] dataToByteArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native char[] dataToCharArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native short[] dataToShortArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native int[] dataToIntArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native float[] dataToFloatArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native double[] dataToDoubleArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeGenerator.class)
public static native JSObject[] dataToArray(JSObject obj);
@InjectedBy(JSNativeGenerator.class) @InjectedBy(JSNativeGenerator.class)
public static native JSObject wrap(byte value); public static native JSObject wrap(byte value);

View File

@ -361,6 +361,13 @@ class JSClassProcessor {
} }
} }
boolean returnByRef = method.getAnnotations().get(JSByRef.class.getName()) != null;
if (returnByRef && !typeHelper.isSupportedByRefType(method.getResultType())) {
diagnostics.error(callLocation, "Method {{m0}} is marked with @JSByRef, but does not return valid "
+ "array type");
return false;
}
requireJSBody(diagnostics, method); requireJSBody(diagnostics, method);
MethodReference delegate = repository.methodMap.get(method.getReference()); MethodReference delegate = repository.methodMap.get(method.getReference());
if (delegate == null) { if (delegate == null) {
@ -387,7 +394,7 @@ class JSClassProcessor {
newInvoke.setArguments(newArgs.toArray(new Variable[0])); newInvoke.setArguments(newArgs.toArray(new Variable[0]));
replacement.add(newInvoke); replacement.add(newInvoke);
if (result != null) { if (result != null) {
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), returnByRef);
copyVar(result, invoke.getReceiver(), invoke.getLocation()); copyVar(result, invoke.getReceiver(), invoke.getLocation());
} }
@ -406,7 +413,7 @@ class JSClassProcessor {
Variable result = invoke.getReceiver() != null ? program.createVariable() : null; Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation()); addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation());
if (result != null) { if (result != null) {
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false);
copyVar(result, invoke.getReceiver(), invoke.getLocation()); copyVar(result, invoke.getReceiver(), invoke.getLocation());
} }
return true; return true;
@ -438,7 +445,7 @@ class JSClassProcessor {
addIndexerGet(invoke.getInstance(), marshaller.wrapArgument(callLocation, invoke.getArguments().get(0), addIndexerGet(invoke.getInstance(), marshaller.wrapArgument(callLocation, invoke.getArguments().get(0),
method.parameterType(0), false), result, invoke.getLocation()); method.parameterType(0), false), result, invoke.getLocation());
if (result != null) { if (result != null) {
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false);
copyVar(result, invoke.getReceiver(), invoke.getLocation()); copyVar(result, invoke.getReceiver(), invoke.getLocation());
} }
return true; return true;
@ -520,7 +527,7 @@ class JSClassProcessor {
newInvoke.setArguments(newArguments.toArray(new Variable[0])); newInvoke.setArguments(newArguments.toArray(new Variable[0]));
replacement.add(newInvoke); replacement.add(newInvoke);
if (result != null) { if (result != null) {
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false);
copyVar(result, invoke.getReceiver(), invoke.getLocation()); copyVar(result, invoke.getReceiver(), invoke.getLocation());
} }
@ -681,12 +688,12 @@ class JSClassProcessor {
replacement.clear(); replacement.clear();
if (!callee.hasModifier(ElementModifier.STATIC)) { if (!callee.hasModifier(ElementModifier.STATIC)) {
insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++), insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
ValueType.object(calleeRef.getClassName()))); ValueType.object(calleeRef.getClassName()), false));
} }
Variable[] args = new Variable[callee.parameterCount()]; Variable[] args = new Variable[callee.parameterCount()];
for (int i = 0; i < callee.parameterCount(); ++i) { for (int i = 0; i < callee.parameterCount(); ++i) {
args[i] = marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++), args[i] = marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
callee.parameterType(i)); callee.parameterType(i), false);
} }
insn.setArguments(args); insn.setArguments(args);
if (callee.getResultType() != ValueType.VOID) { if (callee.getResultType() != ValueType.VOID) {

View File

@ -23,7 +23,7 @@ import org.teavm.jso.core.JSArrayReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
public final class JSMethods { final class JSMethods {
public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class, public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class,
JSObject.class, JSObject.class); JSObject.class, JSObject.class);
public static final MethodReference SET = new MethodReference(JS.class, "set", JSObject.class, JSObject.class, public static final MethodReference SET = new MethodReference(JS.class, "set", JSObject.class, JSObject.class,
@ -93,6 +93,21 @@ public final class JSMethods {
public static final MethodReference ARRAY_UNWRAPPER = new MethodReference(JS.class, public static final MethodReference ARRAY_UNWRAPPER = new MethodReference(JS.class,
"arrayUnwrapper", Class.class, Function.class); "arrayUnwrapper", Class.class, Function.class);
public static final MethodReference DATA_TO_BYTE_ARRAY = new MethodReference(JS.class,
"dataToByteArray", JSObject.class, byte[].class);
public static final MethodReference DATA_TO_SHORT_ARRAY = new MethodReference(JS.class,
"dataToShortArray", JSObject.class, short[].class);
public static final MethodReference DATA_TO_CHAR_ARRAY = new MethodReference(JS.class,
"dataToCharArray", JSObject.class, char[].class);
public static final MethodReference DATA_TO_INT_ARRAY = new MethodReference(JS.class,
"dataToIntArray", JSObject.class, int[].class);
public static final MethodReference DATA_TO_FLOAT_ARRAY = new MethodReference(JS.class,
"dataToFloatArray", JSObject.class, float[].class);
public static final MethodReference DATA_TO_DOUBLE_ARRAY = new MethodReference(JS.class,
"dataToDoubleArray", JSObject.class, double[].class);
public static final MethodReference DATA_TO_ARRAY = new MethodReference(JS.class,
"dataToArray", JSObject.class, JSObject[].class);
public static final MethodReference FUNCTION_AS_OBJECT = new MethodReference(JS.class, "functionAsObject", public static final MethodReference FUNCTION_AS_OBJECT = new MethodReference(JS.class, "functionAsObject",
JSObject.class, JSObject.class, JSObject.class); JSObject.class, JSObject.class, JSObject.class);

View File

@ -178,6 +178,43 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
writer.append(")"); writer.append(")");
} }
break; break;
case "dataToByteArray":
writer.append("$rt_wrapArray($rt_bytecls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToShortArray":
writer.append("$rt_wrapArray($rt_shortcls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToCharArray":
writer.append("$rt_wrapArray($rt_charcls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToIntArray":
writer.append("$rt_wrapArray($rt_intcls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToFloatArray":
writer.append("$rt_wrapArray($rt_floatcls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToDoubleArray":
writer.append("$rt_wrapArray($rt_doublecls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
case "dataToArray":
writer.append("$rt_wrapArray($rt_objcls(),").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
default: default:
if (methodRef.getName().startsWith("unwrap")) { if (methodRef.getName().startsWith("unwrap")) {
context.writeExpr(context.getArgument(0), context.getPrecedence()); context.writeExpr(context.getArgument(0), context.getPrecedence());
@ -209,6 +246,28 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
case "unwrapString": case "unwrapString":
method.getResult().propagate(agent.getType("java.lang.String")); method.getResult().propagate(agent.getType("java.lang.String"));
break; break;
case "dataToByteArray":
method.getResult().propagate(agent.getType("[B"));
break;
case "dataToShortArray":
method.getResult().propagate(agent.getType("[S"));
break;
case "dataToCharArray":
method.getResult().propagate(agent.getType("[C"));
break;
case "dataToIntArray":
method.getResult().propagate(agent.getType("[I"));
break;
case "dataToFloatArray":
method.getResult().propagate(agent.getType("[F"));
break;
case "dataToDoubleArray":
method.getResult().propagate(agent.getType("[D"));
break;
case "dataToArray":
method.getResult().propagate(agent.getType("[Ljava/lang/Object;"));
break;
} }
} }

View File

@ -132,7 +132,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
for (int i = 0; i < method.parameterCount(); ++i) { for (int i = 0; i < method.parameterCount(); ++i) {
variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i], variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i],
method.parameterType(i)); method.parameterType(i), false);
} }
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);

View File

@ -218,7 +218,11 @@ class JSValueMarshaller {
return JSMethods.ARRAY_WRAPPER; return JSMethods.ARRAY_WRAPPER;
} }
Variable unwrapReturnValue(CallLocation location, Variable var, ValueType type) { Variable unwrapReturnValue(CallLocation location, Variable var, ValueType type, boolean byRef) {
if (byRef) {
return unwrapByRef(location, var, type);
}
if (type instanceof ValueType.Object) { if (type instanceof ValueType.Object) {
String className = ((ValueType.Object) type).getClassName(); String className = ((ValueType.Object) type).getClassName();
ClassReader cls = classSource.get(className); ClassReader cls = classSource.get(className);
@ -229,6 +233,29 @@ class JSValueMarshaller {
return unwrap(location, var, type); return unwrap(location, var, type);
} }
private Variable unwrapByRef(CallLocation location, Variable var, ValueType type) {
type = ((ValueType.Array) type).getItemType();
if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) {
case BYTE:
return invokeMethod(location, JSMethods.DATA_TO_BYTE_ARRAY, var);
case SHORT:
return invokeMethod(location, JSMethods.DATA_TO_SHORT_ARRAY, var);
case CHARACTER:
return invokeMethod(location, JSMethods.DATA_TO_CHAR_ARRAY, var);
case INTEGER:
return invokeMethod(location, JSMethods.DATA_TO_INT_ARRAY, var);
case FLOAT:
return invokeMethod(location, JSMethods.DATA_TO_FLOAT_ARRAY, var);
case DOUBLE:
return invokeMethod(location, JSMethods.DATA_TO_DOUBLE_ARRAY, var);
default:
break;
}
}
return invokeMethod(location, JSMethods.DATA_TO_ARRAY, var);
}
Variable unwrap(CallLocation location, Variable var, ValueType type) { Variable unwrap(CallLocation location, Variable var, ValueType type) {
if (type instanceof ValueType.Primitive) { if (type instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) type).getKind()) { switch (((ValueType.Primitive) type).getKind()) {
@ -302,6 +329,18 @@ class JSValueMarshaller {
return var; return var;
} }
private Variable invokeMethod(CallLocation location, MethodReference method, Variable var) {
InvokeInstruction invoke = new InvokeInstruction();
invoke.setArguments(var);
invoke.setMethod(method);
invoke.setReceiver(program.createVariable());
invoke.setType(InvocationType.SPECIAL);
invoke.setLocation(location.getSourceLocation());
replacement.add(invoke);
return invoke.getReceiver();
}
private Variable unwrapSingleDimensionArray(CallLocation location, Variable var, ValueType type) { private Variable unwrapSingleDimensionArray(CallLocation location, Variable var, ValueType type) {
Variable result = program.createVariable(); Variable result = program.createVariable();

View File

@ -17,6 +17,7 @@ package org.teavm.jso.test;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -158,6 +159,15 @@ public class ConversionTest {
assertEquals(44, array[1]); assertEquals(44, array[1]);
} }
@Test
public void returnsArrayByRef() {
int[] first = { 23, 42 };
int[] second = rewrap(first);
assertNotSame(first, second);
second[0] = 99;
assertEquals(99, first[0]);
}
@JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = "" @JSBody(params = { "a", "b", "c", "d", "e", "f", "g", "h" }, script = ""
+ "return '' + a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f.toFixed(1) + ':'" + "return '' + a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f.toFixed(1) + ':'"
+ "+ g.toFixed(1) + ':' + h;") + "+ g.toFixed(1) + ':' + h;")
@ -336,4 +346,8 @@ public class ConversionTest {
+ "}" + "}"
+ "};") + "};")
private static native ByRefMutator createByRefMutator(); private static native ByRefMutator createByRefMutator();
@JSByRef
@JSBody(params = "array", script = "return array;")
private static native int[] rewrap(@JSByRef int[] array);
} }