The storage hint model

Remi Forax forax at univ-mlv.fr
Wed Jul 20 16:44:00 UTC 2022


Yes, i know, we have already discuss several models like that. But i think, it's a good idea to re-examine those because i believe they are more attractive today.

My aim here is to try to simplify the .ref/.val model, not to fundamentally change it, .ref is still the default, there is no way to ask for a .val at definition site, etc so most of the design should be very familiar.

The main issue with the .val model is that it presents two *types* to the user while we really want is mostly to flatten the storage and have a precise the method calling convention.
Those two goals are not equals, the first is far more important than the second, to the point where the coding guideline proposed by Brian is to use .ref for the parameters and .val for the fields and arrays.

We still need .val and .ref to be able to specialize generics, right ? No, i don't think so, we technically do not have to pass a .val as type argument to be able to specialize a generic class, we just need to pass a type argument that can be flatten if it's possible.

Let's run this idea, let say that if we have a value class C, we do not need to pass C.val as argument to specialize a List, List<C> is enough.
Then for field/array and parameter, we need to introduce a storage hint saying that the value class must be a Q-type, in the rest of the document, i will use .flat for that.

so instead of writing   
  value class C {
    // ...
  }

  class Container<T> {
    private T value;

    public Container(T value) {
      this.value = value;
    }
  }
  ...
  Container<C.val> container = new Container<C.val>(new C());

we can write instead
  value class C {
    // ...
  }

  class Container<T> {
    private T.flat value;

    public Container(T value) {
      this.value = value;  // may NPE
    }
  }
  ...
  Container<C> container = new Container<C>(new C());    // there is no C.val anymore !


The idea is to align the way, we declare a generics class with the way we declare a classical class, i.e. instead of specializing the T using a C.val as type argument, we can directly use C as type argument and ask for the flattened version of T using T.flat. This is very similar to the way the equivalent non-generics class is currently declared in the .ref/.val model if you replace replace .flat by .val.

  class Container {
    private C.flat value;

    public Container(C value) {
      this.value = value;  // may NPE
    }
  }
  
So it appears that if we do not allow users to specify if a local variable is a .ref or a .val but decide that it's always a .ref and if we are using container hint inside generics classes the same way we are using it on non-generics classes, then we do not need .ref and .val to be types, but only to be storage hints.

And not having .ref and .val to be types greatly simplify the model, because they is no interaction between the type checking and the storage hints, those are two separated concerns. 

So following the actual design, they are 3 différents kind of value class, using encapsulation to declare if a default is available or not

  value class C {
    private default {}
  }
  value class C {
    /* package-private */ default {}
  }
  value class C {
    public default {}
  }

I've used "default" instead of "companion type" here because those are not type anymore.

If flatten, the assignment can be non-atomic or atomic, with atomic being the default:
  
  non-atomic value class NC {
     [modifier] default {}
  }

Because there is only one type C, all the syntax C.class, c.getClass(), instanceof C, etc works as usual.
All Codes that does not use C.flat works as usual, apart from ==, hashCode, synchronized and weak reference.

The storage hint C.flat can be used only in few selected places:
  - as a hint on a field type:
      private C.flat c;

  - as a hint on a field array type:
      private C.flat[] c;

  - when creating an array
      new C.flat[16]

  - as a hint on a parameter type of a method
      void foo(C.flat c) { ... }
     
In terms of code generation, .flat is equivalent to asking a Q-type and adding a checkcast (or equivalent) from the Q-type to the L-type (or vice-versa) for each read/write of the field, the array cell or the parameter (for the parameter it can be done once).

I really think that the .flat model is far easier to use than the .ref/.val model because it untangle the notion of value type with the notion of container hints and it seems to me that a JIT able to propagate/merge the Q-types should generate an assembly code as efficient as with the .ref/.val model.

And obviously, i may have forgotten something invalidating the whole design, please shoot.

regards,
Rémi


More information about the valhalla-spec-experts mailing list