[External] : Re: Consolidating the user model

John Rose john.r.rose at oracle.com
Thu Nov 4 01:34:52 UTC 2021


On Nov 3, 2021, at 4:05 PM, Dan Smith <daniel.smith at oracle.com<mailto:daniel.smith at oracle.com>> wrote:

(It is, I suppose, part of the model that objects of a given class all have a finite, matching layout when accessed by value, even if the details of that layout are kept abstract. Which is why value types are monomorphic and you need reference types for polymorphism.)

The fact that the VM often discards object headers at runtime is a pure optimization.

Let’s see what happens if we say that (a) bare values have headers and (b) Object::getClass allows the user to observe part of the header contents.

It follows then that the expression aPointVal.getClass() will show the contents of aPointVal’s header, even if it is a compile-time constant.

Point pv = new Point(42,42);  // “class Point” is the definition of Point
assert pv.getClass() == Point.class;  // ok, that’s certainly the class
assert pv.getClass() != Point.ref.class;  // and it’s not a ref, so good

That is all fine.  There’s a little hiccup when you “box” the point and get the same Class mirror even though the “header” is a very real-heap resident value now:

Point.ref pr = pv;  // same object… now it’s on the heap, though, with a real live heap header
assert pr.getClass() == Point.class;  // same class, but...
assert pr.getClass() != Point.ref.class;  // we suppress any distinction the heap header might provide

There’s a bigger hiccup when you compare all that with good old int:

int iv = 42;  // “class int” is NOT a thing, but “class Integer” is
assert iv.getClass() != int.class;  // because int is not a class
assert iv.getClass() == Integer.class;  // ah, there’s the class!
assert iv.getClass() == int.ref.class;  // this works differently from Point
assert ((Object)iv).getClass() == pr.getClass();  // this should be true also, right?

And to finish out the combinations:

int.ref ir = iv;  // same object… now it’s on the heap, though, with a real live heap header
assert ir.getClass() == Integer.class;  // same class
assert ir.getClass() == int.ref.class;  // and this time it’s a ref-class (only for classic primitives)
assert ir.getClass() != int.class;

All this has some odd irregularities when you compare what Point does and what int does.  And yet it’s probably the least-bad thing we can do.

A bad response would be to follow the bad precedent of ir.getClass() == Integer.class off the cliff, and have pv.getClass() and pr.getClass() return Point.ref.class.  That way, getClass() only returns a ref.  Get it, see, getClass() can only return reference types.  The rejoinder (which Brian made to me when I aired it) is devastating:  Point.class is the class, not Point.ref.class, and the method is named “get-class”.

Another approach would be to fiddle with the definitions of val.getClass(), so as to align iv.getClass() with pv.getClass() with their non-ref types.  But that still leaves pv.getClass() unaligned (in its non-ref-ness) with ir.getClass() (in its ref-ness).  We still expect Point.class as the answer from *both* pr.getClass() and pv.getClass().

Or we could try to make the problem go away by simply outlawing (statically) instances of expr.getClass() that expose inconvenient answers.  Such moves score high on the “Those Idiots” score card.  And they still doesn’t align the ref-ness of pr.getClass() vs. ir.getClass().

Maybe we only earn partial Idiot Points if we outlaw iv.getClass() but allow pv.getClass()?  Same amount of seam, different shape of seam, IMO.

Another source of constraint is that we expect that up-casting anything to Object and then re-querying should not change the answer.  (This is another way of saying that the header should stay the same whether it is in the heap or not.)  It is one of the reasons that iv.getClass() should not return int.class.

assert ((Object)pv).getClass() == pv.getClass();  // this should be true also, right?
assert ((Object)pr).getClass() == pr.getClass();  // this should be true also, right?
assert ((Object)iv).getClass() == iv.getClass();  // this should be true also, right?
assert ((Object)ir).getClass() == ir.getClass();  // this should be true also, right?

This is an over-constrained problem.  I don’t know how to make it look more regular, and I think (after doing some more exhaustive analysis off-line) there aren’t any other ideas we haven’t examined.

(I’m saying that partly in a superstitious hope that, having said it, someone will of course prove me wrong.)


I'm claiming this picture makes explaining the feature harder, unnecessarily. An unhoused value floating around somewhere that I can somehow have a reference to strikes me as quite exotic. Tell me it's just an object and I feel calmer.

Yes, it's just an object. :-)

But not quite how you mean. The new feature here is working with objects *directly*, without references. I think one thing you're struggling with is that your concept of "object" includes the reference, and if we take that away, it doesn't quite seem like an object anymore.

The lack of “null” in the value set is a small but persistent hint that something has changed in the object representation.

We can double down on the model that a val-object has a header.  It’s not in the heap; it has a statically defined value; it exists (if at all) to assist with Object::getClass and the other methods as needed.  It feeds getClass with the val-projection, not the ref-projection.

We are so sorry, Mr. int.  You don’t really pass as a primitive class.  If an int has a header (on stack or on heap), it feeds getClass with the ref-projection Integer.class, not the val-projection int.class, because your class is Integer, a ref-type (one of 8 or 9 such types).  It’s a seam.

BTW, here’s another look at the difference between Mr. int and Mr. Point:

var pv = new Point(42,42);  // var infers Point (a val type)
assert new Point(42,42).getClass() == Point.class;  // OK

//var pr = new Point.ref(42,42);  // nope, Point.ref is not “class Point”
//assert new Point.ref(42,42).getClass() == Point.ref.class;  // cannot ask this question

var ir = new Integer(42);  // var infers Integer (a ref type)
assert new Integer(42).getClass() == Integer.class;  // OK, but I don’t like Integer as much as Point

//var iv = new int(42);  // sorry, Mr. int, you don’t get to play there
//assert new int(42).getClass() == int.class;  // cannot ask this question

Did I get all the details right, Dan and Brian?

— John


More information about the valhalla-spec-observers mailing list