diff --git a/jso/core/src/main/java/org/teavm/jso/JSByRef.java b/jso/core/src/main/java/org/teavm/jso/JSByRef.java index 9c43d264b..3092fac1d 100644 --- a/jso/core/src/main/java/org/teavm/jso/JSByRef.java +++ b/jso/core/src/main/java/org/teavm/jso/JSByRef.java @@ -26,6 +26,6 @@ import java.lang.annotation.Target; * to: byte[], short[], char[], int[], float[], double[] or T[], where T is JSObject.

*/ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PARAMETER) +@Target({ ElementType.PARAMETER, ElementType.METHOD }) public @interface JSByRef { } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 0b94c1b8c..2ef5e5c4b 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -35,6 +35,34 @@ final class JS { @InjectedBy(JSNativeGenerator.class) 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) public static native JSObject wrap(byte value); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index 48b91b2ae..b7878bcb5 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -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); MethodReference delegate = repository.methodMap.get(method.getReference()); if (delegate == null) { @@ -387,7 +394,7 @@ class JSClassProcessor { newInvoke.setArguments(newArgs.toArray(new Variable[0])); replacement.add(newInvoke); if (result != null) { - result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); + result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), returnByRef); copyVar(result, invoke.getReceiver(), invoke.getLocation()); } @@ -406,7 +413,7 @@ class JSClassProcessor { Variable result = invoke.getReceiver() != null ? program.createVariable() : null; addPropertyGet(propertyName, invoke.getInstance(), result, invoke.getLocation()); if (result != null) { - result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); + result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false); copyVar(result, invoke.getReceiver(), invoke.getLocation()); } return true; @@ -438,7 +445,7 @@ class JSClassProcessor { addIndexerGet(invoke.getInstance(), marshaller.wrapArgument(callLocation, invoke.getArguments().get(0), method.parameterType(0), false), result, invoke.getLocation()); if (result != null) { - result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); + result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false); copyVar(result, invoke.getReceiver(), invoke.getLocation()); } return true; @@ -520,7 +527,7 @@ class JSClassProcessor { newInvoke.setArguments(newArguments.toArray(new Variable[0])); replacement.add(newInvoke); if (result != null) { - result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType()); + result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false); copyVar(result, invoke.getReceiver(), invoke.getLocation()); } @@ -681,12 +688,12 @@ class JSClassProcessor { replacement.clear(); if (!callee.hasModifier(ElementModifier.STATIC)) { insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++), - ValueType.object(calleeRef.getClassName()))); + ValueType.object(calleeRef.getClassName()), false)); } Variable[] args = new Variable[callee.parameterCount()]; for (int i = 0; i < callee.parameterCount(); ++i) { args[i] = marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++), - callee.parameterType(i)); + callee.parameterType(i), false); } insn.setArguments(args); if (callee.getResultType() != ValueType.VOID) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java index c978a5612..20d19a3f4 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java @@ -23,7 +23,7 @@ import org.teavm.jso.core.JSArrayReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; -public final class JSMethods { +final class JSMethods { public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class, 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, "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", JSObject.class, JSObject.class, JSObject.class); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java index 7430664d2..71e97fce9 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSNativeGenerator.java @@ -178,6 +178,43 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator writer.append(")"); } 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: if (methodRef.getName().startsWith("unwrap")) { context.writeExpr(context.getArgument(0), context.getPrecedence()); @@ -209,6 +246,28 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator case "unwrapString": method.getResult().propagate(agent.getType("java.lang.String")); 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; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index c6aeb0f41..5e4ab6b3d 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -132,7 +132,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { for (int i = 0; i < method.parameterCount(); ++i) { variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i], - method.parameterType(i)); + method.parameterType(i), false); } basicBlock.addAll(marshallInstructions); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java index 39c9042fa..e090040b7 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java @@ -218,7 +218,11 @@ class JSValueMarshaller { 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) { String className = ((ValueType.Object) type).getClassName(); ClassReader cls = classSource.get(className); @@ -229,6 +233,29 @@ class JSValueMarshaller { 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) { if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive) type).getKind()) { @@ -302,6 +329,18 @@ class JSValueMarshaller { 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) { Variable result = program.createVariable(); diff --git a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java index cde2457e2..ad6a93ea7 100644 --- a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java @@ -17,6 +17,7 @@ package org.teavm.jso.test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import org.junit.Test; import org.junit.runner.RunWith; @@ -158,6 +159,15 @@ public class ConversionTest { 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 = "" + "return '' + a + ':' + b + ':' + c + ':' + d + ':' + e + ':' + f.toFixed(1) + ':'" + "+ g.toFixed(1) + ':' + h;") @@ -336,4 +346,8 @@ public class ConversionTest { + "}" + "};") private static native ByRefMutator createByRefMutator(); + + @JSByRef + @JSBody(params = "array", script = "return array;") + private static native int[] rewrap(@JSByRef int[] array); }