[m-rev.] for review: allow foreign types to be Java numeric primitive types

Julien Fischer jfischer at opturion.com
Tue Apr 12 11:35:07 AEST 2016


For review by anyone.

Question: the remaining Java primitive types (bool and char) are not 
supported by this change and I can't really see a need for them.  In
the interests of completness should was also support them?

---------------

Allow foreign types to be Java numeric primitive types.

Currently we only allow non-primitive Java types to used with foreign_type
pragmas.  This means that for foreign types that map on to Java numeric types
we have to use their boxed form (e.g. java.lang.Long, java.lang.Float etc) in
the foreign_type pragma.  Doing so results in unnecessary boxing and is a
source of errors (e.g. accidently comparing for reference equality instead of
value equality).

This change causes the MLDS->Java code generator to recognise primitive numeric
foreign_types and generate code that works for them directly.   (In effect
their handling is not really different to how the builtin types are handled.)

doc/reference.texi:
      Change the language so Java foreign types are allowed to be a
      primitive Java numeric type.

compiler/mlds_to_java.m:
      Recognise foreign types that are defined as primitive numeric
      types in Java and generate code that avoids the need for (some)
      boxing of them.

library/array.m:
      Handle arrays of Java bytes, shorts, longs and floats specially.
      (int and double were already handled thus since the Mercury builtin
      types int and float map on to them.)

NEWS:
      Announce the above.

tests/hard_coded/Mmakefile:
tests/hard_coded/test_java_foreign_primitive.{m,exp}:
      Test that foreign types work with primitive numeric types in Java.

Julien.

diff --git a/NEWS b/NEWS
index 5910514..ecc7d7d 100644
--- a/NEWS
+++ b/NEWS
@@ -104,6 +104,9 @@ Changes to the Mercury language:
    any of its procedures, or for any of the procedures they call,
    directly or indirectly.

+* The Java backend now supports defining foreign types as primitive Java
+  numeric types.
+
  Changes to the Mercury standard library:

  * We have added variants of the process_options predicates to the getopt
diff --git a/compiler/mlds_to_java.m b/compiler/mlds_to_java.m
index 068ad80..854be59 100644
--- a/compiler/mlds_to_java.m
+++ b/compiler/mlds_to_java.m
@@ -2770,10 +2770,15 @@ get_java_type_initializer(Type) = Initializer :-
          Type = mlds_native_bool_type,
          Initializer = "false"
      ;
+        Type = mlds_foreign_type(ForeignLangType),
+        ( if java_primitive_foreign_language_type(ForeignLangType, _, _, _)
+        then Initializer = "0"
+        else Initializer = "null"
+        )
+    ;
          ( Type = mlds_mercury_array_type(_)
          ; Type = mlds_cont_type(_)
          ; Type = mlds_commit_type
-        ; Type = mlds_foreign_type(_)
          ; Type = mlds_class_type(_, _, _)
          ; Type = mlds_array_type(_)
          ; Type = mlds_mostly_generic_array_type(_)
@@ -4891,9 +4896,8 @@ java_builtin_type(Type, "int", "java.lang.Integer", "intValue") :-
  java_builtin_type(Type, "boolean", "java.lang.Boolean", "booleanValue") :-
      Type = mlds_native_bool_type.

-    % io.state and store.store(S) are dummy variables
-    % for which we pass an arbitrary integer. For this
-    % reason they should have the Java type `int'.
+    % io.state and store.store(S) are dummy variables for which we pass an
+    % arbitrary integer. For this reason they should have the Java type `int'.
      %
  java_builtin_type(Type, "int", "java.lang.Integer", "intValue") :-
      % The test for defined/3 is logically redundant since all dummy
@@ -4902,6 +4906,59 @@ java_builtin_type(Type, "int", "java.lang.Integer", "intValue") :-
      Type = mercury_type(defined_type(_, _, _), TypeCtorCat, _),
      TypeCtorCat = ctor_cat_builtin_dummy.

+    % Handle foreign types that map on to Java's primitive numeric types
+    % specially since we want to avoid boxing them where possible for
+    % performance reasons.
+    %
+java_builtin_type(Type, PrimitiveType, BoxedType, UnboxMethod) :-
+    Type = mlds_foreign_type(ForeignLangType),
+    java_primitive_foreign_language_type(ForeignLangType, PrimitiveType,
+        BoxedType, UnboxMethod).
+
+:- pred java_primitive_foreign_language_type(foreign_language_type::in, string::out,
+    string::out, string::out) is semidet.
+
+java_primitive_foreign_language_type(ForeignLangType, PrimitiveType,
+        BoxedType, UnboxMethod) :-
+    require_complete_switch [ForeignLangType] (
+        ForeignLangType = java(java_type(JavaForeignType))
+    ;
+        ForeignLangType = c(_),
+        unexpected($file, $pred, "foreign_type for C")
+    ;
+        ForeignLangType = csharp(_),
+        unexpected($file, $pred, "foreign_type for C#")
+    ;
+        ForeignLangType = erlang(_),
+        unexpected($file, $pred, "foreign_type for Erlang")
+    ),
+    PrimitiveType = string.strip(JavaForeignType),
+    (
+        PrimitiveType = "byte",
+        BoxedType = "java.lang.Byte",
+        UnboxMethod = "byteValue"
+    ;
+        PrimitiveType = "short",
+        BoxedType = "java.lang.Short",
+        UnboxMethod = "shortValue"
+    ;
+        PrimitiveType = "int",
+        BoxedType = "java.lang.Integer",
+        UnboxMethod = "intValue"
+    ;
+        PrimitiveType = "long",
+        BoxedType = "java.lang.Long",
+        UnboxMethod = "longValue"
+    ;
+        PrimitiveType = "float",
+        BoxedType = "java.lang.Float",
+        UnboxMethod = "floatValue"
+    ;
+        PrimitiveType = "double",
+        BoxedType = "java.lang.Double",
+        UnboxMethod = "doubleValue"
+    ).
+
  :- pred output_std_unop(java_out_info::in, builtin_ops.unary_op::in,
      mlds_rval::in, io::di, io::uo) is det.

diff --git a/doc/reference_manual.texi b/doc/reference_manual.texi
index 807d3c8..2209126 100644
--- a/doc/reference_manual.texi
+++ b/doc/reference_manual.texi
@@ -8495,7 +8495,9 @@ A Java @samp{pragma foreign_type} declaration has the form:
  :- pragma foreign_type("Java", @var{MercuryTypeName}, "@var{JavaType}").
  @end example

-The @var{JavaType} can be any accessible non-primitive Java type.
+The @var{JavaType} can be any accessible non-primitive Java type or one of the
+following primitive Java numeric types: @code{byte}, @code{short}, @code{int},
+ at code{long}, @code{float} or @code{double}.

  The effect of this declaration is that Mercury values of type
  @var{MercuryTypeName} will be passed to and from Java foreign_procs
diff --git a/library/array.m b/library/array.m
index 36bd599..fd604f5 100644
--- a/library/array.m
+++ b/library/array.m
@@ -1079,6 +1079,34 @@ ML_new_array(int Size, Object Item, boolean fill)
          }
          return as;
      }
+    if (Item instanceof Byte) {
+        byte[] as = new byte[Size];
+        if (fill) {
+            java.util.Arrays.fill(as, (Byte) Item);
+        }
+        return as;
+    }
+    if (Item instanceof Short) {
+        short[] as = new short[Size];
+        if (fill) {
+            java.util.Arrays.fill(as, (Short) Item);
+        }
+        return as;
+    }
+    if (Item instanceof Long) {
+        long[] as =  new long[Size];
+        if (fill) {
+            java.util.Arrays.fill(as, (Long) Item);
+        }
+        return as;
+    }
+    if (Item instanceof Float) {
+        float[] as = new float[Size];
+        if (fill) {
+            java.util.Arrays.fill(as, (Float) Item);
+        }
+        return as;
+    }
      Object[] as = new Object[Size];
      if (fill) {
          java.util.Arrays.fill(as, Item);
@@ -1109,6 +1137,26 @@ ML_unsafe_new_array(int Size, Object Item, int IndexToSet)
          as[IndexToSet] = (Boolean) Item;
          return as;
      }
+    if (Item instanceof Byte) {
+        byte[] as = new byte[Size];
+        as[IndexToSet] = (Byte) Item;
+        return as;
+    }
+    if (Item instanceof Short) {
+        short[] as = new short[Size];
+        as[IndexToSet] = (Short) Item;
+        return as;
+    }
+    if (Item instanceof Long) {
+        long[] as = new long[Size];
+        as[IndexToSet] = (Long) Item;
+        return as;
+    }
+    if (Item instanceof Float) {
+        float[] as = new float[Size];
+        as[IndexToSet] = (Float) Item;
+        return as;
+    }
      Object[] as = new Object[Size];
      as[IndexToSet] = Item;
      return as;
@@ -1127,6 +1175,14 @@ ML_array_size(Object Array)
          return ((char[]) Array).length;
      } else if (Array instanceof boolean[]) {
          return ((boolean[]) Array).length;
+    } else if (Array instanceof byte[]) {
+        return ((byte[]) Array).length;
+    } else if (Array instanceof short[]) {
+        return ((short[]) Array).length;
+    } else if (Array instanceof long[]) {
+        return ((long[]) Array).length;
+    } else if (Array instanceof float[]) {
+        return ((float[]) Array).length;
      } else {
          return ((Object[]) Array).length;
      }
@@ -1183,6 +1239,46 @@ ML_array_resize(Object Array0, int Size, Object Item)
              Array[i] = (Boolean) Item;
          }
          return Array;
+    }
+    if (Array0 instanceof byte[]) {
+        byte[] arr0 = (byte[]) Array0;
+        byte[] Array = new byte[Size];
+
+        System.arraycopy(arr0, 0, Array, 0, Math.min(arr0.length, Size));
+        for (int i = arr0.length; i < Size; i++) {
+            Array[i] = (Byte) Item;
+        }
+        return Array;
+    }
+    if (Array0 instanceof short[]) {
+        short[] arr0 = (short[]) Array0;
+        short[] Array = new short[Size];
+
+        System.arraycopy(arr0, 0, Array, 0, Math.min(arr0.length, Size));
+        for (int i = arr0.length; i < Size; i++) {
+            Array[i] = (Short) Item;
+        }
+        return Array;
+    }
+    if (Array0 instanceof long[]) {
+        long[] arr0 = (long[]) Array0;
+        long[] Array = new long[Size];
+
+        System.arraycopy(arr0, 0, Array, 0, Math.min(arr0.length, Size));
+        for (int i = arr0.length; i < Size; i++) {
+            Array[i] = (Long) Item;
+        }
+        return Array;
+    }
+    if (Array0 instanceof float[]) {
+        float[] arr0 = (float[]) Array0;
+        float[] Array = new float[Size];
+
+        System.arraycopy(arr0, 0, Array, 0, Math.min(arr0.length, Size));
+        for (int i = arr0.length; i < Size; i++) {
+            Array[i] = (Float) Item;
+        }
+        return Array;
      } else {
          Object[] arr0 = (Object[]) Array0;
          Object[] Array = new Object[Size];
@@ -1601,6 +1697,14 @@ semidet_lookup(Array, Index, Item) :-
          Item = ((char[]) Array)[Index];
      } else if (Array instanceof boolean[]) {
          Item = ((boolean[]) Array)[Index];
+    } else if (Array instanceof byte[]) {
+        Item = ((byte[]) Array)[Index];
+    } else if (Array instanceof short[]) {
+        Item = ((short[]) Array)[Index];
+    } else if (Array instanceof long[]) {
+        Item = ((long[]) Array)[Index];
+    } else if (Array instanceof float[]) {
+        Item = ((float[]) Array)[Index];
      } else {
          Item = ((Object[]) Array)[Index];
      }
@@ -1667,6 +1771,14 @@ set(Index, Item, !Array) :-
          ((char[]) Array0)[Index] = (Character) Item;
      } else if (Array0 instanceof boolean[]) {
          ((boolean[]) Array0)[Index] = (Boolean) Item;
+    } else if (Array0 instanceof byte[]) {
+        ((byte[]) Array0)[Index] = (Byte) Item;
+    } else if (Array0 instanceof short[]) {
+        ((short[]) Array0)[Index] = (Short) Item;
+    } else if (Array0 instanceof long[]) {
+        ((long[]) Array0)[Index] = (Long) Item;
+    } else if (Array0 instanceof float[]) {
+        ((float[]) Array0)[Index] = (Float) Item;
      } else {
          ((Object[]) Array0)[Index] = Item;
      }
diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile
index be0059d..1e19c0e 100644
--- a/tests/hard_coded/Mmakefile
+++ b/tests/hard_coded/Mmakefile
@@ -463,7 +463,7 @@ else
  	CTGC_PROGS =
  endif

-# Programs that deal with large amounts of datas need tail recursion. Don't
+# Programs that deal with large amounts of data need tail recursion. Don't
  # execute them in grades that do not allow tail recursion, since their failures
  # would be spurious.

@@ -477,13 +477,14 @@ else
  	BIG_DATA_PROGS =
  endif

-# Tests of the Java foreign language interface only
-# work in Java grades
+# Tests of the Java foreign language interface only work in Java grades.
+#
  ifeq "$(filter java%,$(GRADE))" ""
  	JAVA_PROGS =
  else
  	JAVA_PROGS = \
-		java_test
+		java_test \
+		test_java_foreign_primitive
  endif

  # Fact tables currently work only in the C grades.
diff --git a/tests/hard_coded/test_java_foreign_primitive.exp b/tests/hard_coded/test_java_foreign_primitive.exp
new file mode 100644
index 0000000..6ce7700
--- /dev/null
+++ b/tests/hard_coded/test_java_foreign_primitive.exp
@@ -0,0 +1,6 @@
+32
+32
+32
+32
+32.0
+32.0
diff --git a/tests/hard_coded/test_java_foreign_primitive.m b/tests/hard_coded/test_java_foreign_primitive.m
new file mode 100644
index 0000000..4da697c
--- /dev/null
+++ b/tests/hard_coded/test_java_foreign_primitive.m
@@ -0,0 +1,198 @@
+%----------------------------------------------------------------------------%
+% vim: ts=4 sw=4 et ft=mercury
+%----------------------------------------------------------------------------%
+%
+% Test support for primitive numeric types as foreign_types in the Java
+% grade.
+%
+%----------------------------------------------------------------------------%
+
+:- module test_java_foreign_primitive.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+%----------------------------------------------------------------------------%
+%----------------------------------------------------------------------------%
+
+:- implementation.
+
+main(!IO) :-
+    io.print_line(byte_to_string(byte(16) `plus_byte` byte(16)), !IO),
+    io.print_line(short_to_string(short(16) `plus_short` short(16)), !IO),
+    io.print_line(int_to_string(int(16) `plus_int` int(16)), !IO),
+    io.print_line(long_to_string(long(16) `plus_long` long(16)), !IO),
+    io.print_line(float_to_string(float(16) `plus_float` float(16)), !IO),
+    io.print_line(double_to_string(double(16) `plus_double` double(16)), !IO).
+
+%----------------------------------------------------------------------------%
+
+:- type jbyte.
+:- type jshort.
+:- type jint.
+:- type jlong.
+:- type jfloat.
+:- type jdouble.
+
+:- pragma foreign_type("Java", jbyte, "byte").
+:- pragma foreign_type("Java", jshort, "short").
+:- pragma foreign_type("Java", jint, "int").
+:- pragma foreign_type("Java", jlong, "long").
+:- pragma foreign_type("Java", jfloat, "float").
+:- pragma foreign_type("Java", jdouble, "double").
+
+%----------------------------------------------------------------------------%
+
+:- func byte(int) = jbyte.
+:- pragma foreign_proc("Java",
+    byte(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = (byte) A;
+").
+
+:- func short(int) = jshort.
+:- pragma foreign_proc("Java",
+    short(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = (short) A;
+").
+
+:- func int(int) = jint.
+:- pragma foreign_proc("Java",
+    int(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = A;
+").
+
+:- func long(int) = jlong.
+:- pragma foreign_proc("Java",
+    long(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = A;
+").
+
+:- func float(int) = jfloat.
+:- pragma foreign_proc("Java",
+    float(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = (float) A;
+").
+
+:- func double(int) = jdouble.
+:- pragma foreign_proc("Java",
+    double(A::in) = (B::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    B = (double) A;
+").
+
+%----------------------------------------------------------------------------%
+
+:- func plus_byte(jbyte, jbyte) = jbyte.
+:- pragma foreign_proc("Java",
+    plus_byte(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = (byte) (A + B);
+").
+
+:- func plus_short(jshort, jshort) = jshort.
+:- pragma foreign_proc("Java",
+    plus_short(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = (short) (A + B);
+").
+
+:- func plus_int(jint, jint) = jint.
+:- pragma foreign_proc("Java",
+    plus_int(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = A + B;
+").
+
+:- func plus_long(jlong, jlong) = jlong.
+:- pragma foreign_proc("Java",
+    plus_long(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = A + B;
+").
+
+:- func plus_float(jfloat, jfloat) = jfloat.
+:- pragma foreign_proc("Java",
+    plus_float(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = A + B;
+").
+
+:- func plus_double(jdouble, jdouble) = jdouble.
+:- pragma foreign_proc("Java",
+    plus_double(A::in, B::in) = (C::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    C = A + B;
+").
+
+%----------------------------------------------------------------------------%
+
+:- func byte_to_string(jbyte) = string.
+:- pragma foreign_proc("Java",
+    byte_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Integer.toString(N);
+").
+
+:- func short_to_string(jshort) = string.
+:- pragma foreign_proc("Java",
+    short_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Integer.toString(N);
+").
+
+:- func int_to_string(jint) = string.
+:- pragma foreign_proc("Java",
+    int_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Integer.toString(N);
+").
+
+:- func long_to_string(jlong) = string.
+:- pragma foreign_proc("Java",
+    long_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Long.toString(N);
+").
+
+:- func float_to_string(jfloat) = string.
+:- pragma foreign_proc("Java",
+    float_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Float.toString(N);
+").
+
+:- func double_to_string(jdouble) = string.
+:- pragma foreign_proc("Java",
+    double_to_string(N::in) = (S::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    S = java.lang.Double.toString(N);
+").
+
+%----------------------------------------------------------------------------%
+:- end_module test_java_foreign_primitive.
+%----------------------------------------------------------------------------%


More information about the reviews mailing list