Author's note: This document is now quite out of date. Its left here for the amusement of history.
I read with interest the JGNWG's proposal for improving Java's numerical computation ( JGNWG) , and several other articles on this topic, including Gosling's preliminary proposal. Unfortunately, I couldn't find the current version of Sun's PEJFPS online. Below are some comments/thoughts on this topic.
Regardless of any extensions to Java to support lightweight classes and new numeric types, boxing/unboxing will still be a very common operation, and it is important to look for ways to improve efficiency in this area.
A number of early AI languages used tag bits to encode whether a word in memory has value or reference semantics. A typical scheme might use two bits of a 32 bit word, e.g. with the encoding:
Bits Meaning
--------------
00 reference
01 integer
10 float
For references, the remaining 30 bits of the word act as a pointer to the referenced object (pointers are invariably aligned to 4 byte boundaries in any case, so reducing pointer resolution by 2 bits has little effect). For int values, the remaining 30 bits encode a 29 bit integer and one sign bit. Similarly for floats.
Such schemes were used because they eliminated the need for performing boxing/unboxing and heap allocation for single precision integers and floats. In addition, basic arithmetic operations can be performed on such tagged values using only one or two additional instructions. For example, if the tag bits are held at the low end, an integer add can be implemented as:
add(a,b) :
add a,b -> a ;; (should check for overflow...)
decrement a, 1 ;; fix up tag bits
This simple trick of using tag bits to differentiate primitive values from reference types might be extremely effective in a JVM implementation. One can imagine a JVM in which the instructions for:
new Integer(x);
were handled using:
if (num_used_bits(x) > 29)
return (a new Integer object made on the heap)
otherwise
return (x << 2) | 1
And likewise for float. The impact of this style of implementation could be very significant, since it would vastly speed up most boxing/unboxing operations, and would make the use of ints and floats in datastructures such as Vectors and Hashtables hugely more efficient. In addition, this implementation trick could be almost completely hidden from Java programs running on the machine - by modifying primitives such as instanceof, getClass, etc. to handle tagged values correctly. This approach could also significantly increase the performance of other high level languages implemented on top of the JVM.
A drawback of this approach is that requires modifying quite a few system methods to handle references with tag bits. Object, Float and Integer would require the largest changes, as well as the few places in the JVM where runtime type checks are performed, instructions like instanceof and checkcast, and also the garbage collector. Normal method dispatch would not be affected.
A major barrier preventing this style of implementation is that it is currently incompatible with the semantics of object identity defined by the Java language spec. For example, in a JVM that used value tagging as described above:
new Integer(10) == new Integer(10)
would return true, whereas
new Integer(0xffffffff) == new Integer(0xffffffff)
would return false!
This problem is similar to the identity problems raised by immutable/lightweight classes.
I believe it is important to ensure that the Java spec gives JVM implementors the freedom to adopt value tagging to reduce the cost of boxing/unboxing operations if they choose. Three possible options are:
Alter the defined semantics of the constructors and the == operator for Integer, Float, Long and Double - people probably don't rely on existing semantics too much in any case.
Introduce new classes, e.g. LightweightInteger, LightweightFloat, with different == semantics.
Introduce new static methods in Integer, Float, etc. that can either return new instances or reuse existing instances, e.g. something like
Returns an instance of Integer whose intValue is v. The returned instance may be newly created, or it may be a previously cached instance. i.e. (Integer.getInteger(v) == Integer.getInteger(v)) may evaluate to true or false, depending on the implementation. Using getInteger() may be more efficient than constructing an Integer object explicitly.
This final alternative seems adequate, 100% backwards compatible, and also easy to carry out.
I agree 100% with the spirit of the JGNWG proposal. However, I think that if we are to extend Java's numeric support, we must make sure to do so in a way that preserves Java's clean lines. Below are some comments on the implementation details for these proposals.
Both proposals talk about the need for operator overloading, and suggest naming conventions for operators. I think that one good option would be to extend the set of special identifier names in the Java Virtual Machine. Currently, "<init>" and "<clinit>" are special identifiers names (S3.8, Java Virtual Machine Spec) used for constructors and for class initializers. This set of special method names could be extended to include:
<plus>
<minus>
<plusAssign>
etc.
Then, in the Java language, such methods can be named using, for example:
public int + (int b) {
...
}
The compiler can convert "+" into "<plus>". This seems to be very consistent with the existing naming mechanism, and also 100% backwards compatible with existing Java classes. (On the other hand, promoting existing legal Java method names to special "operator" status seems problematic from a backwards compatibility standpoint).
The Java 2.0 platform currently has the "strictfp" and "widefp" method modifiers, and a revised specification of the default interpretation for floating point arithmetic. Is this finalized? How does this relate to the modifiers specified by the JGNWG? Unfortunately, Sun's site contains only a sparse level of information on this topic. There is no mention of this on the JGNWG's site.
The JGNWG proposes the four new reserved words: "associativefp", "strictfp", "indigenous" and "anonymous". These constructs clearly provide important functionality, but I do wish that we could come up with more understandable names. I personally prefer the "doubleN" convention of RealJava to "indigenous".
I also don't know if anyone except for extreme experts will know when to use "associativefp" or "anonymous". Perhaps we can come up with names that reflect more about how they are used? (I don't actually understand how the "anonymous" modifier is used).
For multidimension arrays, the JGNWG proposes introducing a large set of array classes such as doubleArray2D. I think there may be a good opportunity for using inner classes here, as is done in the java.awt.geom package. e.g. we could have the classes:
package java.lang.math:
abstract class Array2D {
static class Double { ... }
static class Float { ... }
static class Int { ... }
static class Long { ... }
}
abstract class Array3D {
static class Double { ... }
static class Float { ... }
static class Int { ... }
static class Long { ... }
}
etc.
Where the Array2D and Array3D base classes would provide abstract operations common to all types of 2D / 3D arrays...
A major problem with this approach is that it will not extend gracefully to handle new user defined lightweight classes - for example, complex, interval, etc. For each new lightweight type, the application writer will have to create a new array class for that type. This is very inconvenient. One of the major uses of lightweight structs is in arrays.
We might instead need a new mechanism for handling packed value arrays. For example, we could borrow the [1..10] style array syntax from Pascal, giving us something like:
public int[..][..] revolve(double matrix[..][..]);
int result[..][..];
// create a new packed array
result = new int matrix[0..10][0..20];
etc.
i.e. ".." within [] indicates a packed value array (assigning into the array always has copy semantics).
At the JVM level, this would require new instructions, e.g. newpackedarray, and also modifications to type descriptors.
Another approach would be to introduce templates into the language - then we could just have a single array template :-)