Annotate an interface with
Layout to generate an implementation of the interface which
uses object-model properties.
Layout allows you to use the object-model in a similar way
to writing a normal Java class for statically declared and implementation-specific fields. Most
methods generated from an
Layout-annotated interface are suitable for use on the
fast-path.
The name of the interface should end with 'Layout'.
@Layout
private interface RectLayout {
}
The generated class is named with the name of the interface and then
-Impl. A singleton
instance of the interface,
-Impl.INSTANCE is available as a static field in the class.
RectLayoutImpl.INSTANCE;
Factory method
A factory method named
create- and then the name of the layout creates instances of the
layout. It returns a
DynamicObject, not an instance of the interface.
DynamicObject createRect(int x, int y, int width, int height);
Alternative constructor method
As an alternative to the
create- factory, you can declare a method named
build,
which returns the arguments packed for
DynamicObjectFactory.newInstance(Object...).
Object[] build(int x, int y, int width, int height);
This is particularly useful when the
DynamicObjectFactory is not statically known or some
generic allocation node taking
Object[] arguments is used to create objects.
DynamicObjectFactory factory = getCachedFactory(type);
return factory.newInstance(RectLayoutImpl.INSTANCE.build(x, y, width, height));
Guards
Guards can tell you if an object is using layout. Guards are defined for
DynamicObject,
the more general
Object which first checks if the arguments is a
DynamicObject,
and
ObjectType, which you can get through the shape of a
DynamicObject. To add a
guard, define the method in your interface.
boolean isRect(DynamicObject object);
boolean isRect(Object object);
boolean isRect(ObjectType objectType);
Properties
To add properties, define a getter and setter, and add a parameter to the factory method.
DynamicObject createRect(int x, int y, int width, int height);
int getX(DynamicObject object);
void setX(DynamicObject object, int value);
int getWidth(DynamicObject object);
void setWidth(DynamicObject object, int value);
If you don't define a setter, the property will be final. This may improve the performance of the
property.
Nullable Properties
By default, properties are non-nullable, which means that they always need an instance of an
object and they cannot be assigned the value
null. This has performance benefits in the
implementation of the object-model.
To make a property nullable so that you can assign
null to it, annotate the constructor
parameter with
Nullable.
DynamicObject createObject(@Nullable Object nullableProperty);
Volatile Properties
To define a property with volatile semantics, in the sense of the Java Language Specification
section 8.3.1.4, annotate the constructor parameter with
Volatile. A property annotated
as volatile also allows you to define atomic operation methods in your layout interface for that
property. Methods available are
compareAndSet, in the sense of
AtomicReference.compareAndSet(V, V), and
getAndSet, in the sense of
AtomicReference.getAndSet(V).
boolean compareAndSetWidth(DynamicObject object,
int expectedValue, int newValue);
int getAndSet(DynamicObject object, int value);
Volatile properties generally have lower performance than the default non-volatile properties.
Semi-Final Properties
It is possible to define a 'back-door' and unsafe setter for otherwise-final properties by
appending
-Unsafe to the setter name.
void setValueUnsafe(DynamicObject object, Object value);
Final and semi-final properties may be assumed by a dynamic compiler to not change for a given
instance of an object after it is constructed. Unsafe setters are therefore unsafe as a
modification to the property could be ignored by the dynamic compiler. You should only use unsafe
setters if you have reasoned that it is not possible for the dynamic compiler to compile a
reference to the object and the property before the unsafe setter is used. One use-case is
closing cycles in class graphs, such as the classic class-of-class-is-class problem, where you
normally want the class property to be final for performance but just as the graph is created
this one cycle needs to be closed.
Errors due to the incorrect use of unsafe getters are likely to be non-deterministic and
difficult to isolate. Consider making properties temporarily non-final with a conventional getter
if stale value are experienced in dynamically compiled code.
Shape Properties
A shape property is a property that is shared between many objects and does not frequently
change. One intended use-case is a property to store the class of an object, which is likely
shared between many objects and likely does not change after the object is created.
Shape properties should be cached against an object's shape as there is an extra level of
indirection used to look up their value for an object. They may save space as they are not stored
for all instances.
It is important to note that changing a shape-property for an existing object is both not a
fast-path operation, and depending on the design of your interpreter is likely to invalidate
caches.
When shape properties are used there is an extra level of indirection, in that a
DynamicObjectFactory (referred to as the shape, because it is the shape that the factory
object contains that is used to look up shape properties) is created by the layout and then used
when creating new instances. As shape properties are set and changed, multiple factories will be
created and it is up to the user to store and supply these as needed.
Consider the example of a Java-style object, with a class and a hash code. The class would be a
shape property, as many objects will share the same class, and the hash code will be a normal
property.
Shape properties are created by parameters in the method that creates the shape. The factory
method then accepts an instance of a factory when creating the object, which is how the instance
knows the value of the class property to use. A getter for a shape property can be defined as
normal.
@Layout
interface JavaObjectLayout {
DynamicObjectFactory createJavaObjectShape(JavaClass klass);
DynamicObject createJavaObject(DynamicObjectFactory factory, int hashCode);
JavaClass getKlass(DynamicObjectFactory factory);
JavaClass getKlass(ObjectType objectType);
JavaClass getKlass(DynamicObject object);
int getHashCode(DynamicObject object);
}
When we load our Java interpreter we need to set the class property of the
Class object
to be itself. This means in this one isolated, slow-path, case we need to change a shape property
for an object that is already allocated. Getters for shape properties can be defined for the
DynamicObjectFactory, and for the
ObjectType.
Setters for shape properties are more complex, and they are not intended to be used in the fast
path. Setters can be defined on a
DynamicObjectFactory, in which case they return a new
factory, or on a
DynamicObject, in which they they change the shape of the object. This
is a slow-path operation and is likely to invalidate caches in your interpreter.
DynamicObjectFactory setKlass(DynamicObjectFactory factory, JavaClass value);
void setKlass(DynamicObject object, JavaClass value);
Apply this to our example with Java classes:
javaClassObject = JavaObjectImpl.INSTANCE.createJavaObject(
JavaObjectImpl.INSTANCE.createJavaObjectShape(null),
defaultHashCode());
JavaObjectImpl.INSTANCE.setKlass(javaClassObject, javaClassObject);
Layout Inheritance
Inheritance of layout interfaces allows you to model classical class inheritance, such as in a
language like Java. Use normal interface inheritance to make one layout inherit from another. You
then need to add the parameters for super-layouts at the beginning of sub-layout constructor
methods.
Inherited shape properties work in a similar way.
@Layout
interface BaseLayout {
DynamicObject createBase(int a);
boolean isBase(DynamicObject object);
int getA(DynamicObject object);
void setA(DynamicObject object, int value);
}
@Layout
interface SuperLayout extends BaseLayout {
DynamicObject createSuper(int a, int b);
int getB(DynamicObject object);
void setB(DynamicObject object, int value);
}
DynamicObject object = SuperImpl.INSTANCE.createSuper(14, 2);
BaseImpl.INSTANCE.isBase(object);
BaseImpl.INSTANCE.getA(object);
Custom Object-Type Superclass
Generated layouts use custom
ObjectType subclasses internally. The default base class
that is inherited from is simply
ObjectType. You can change this with the
Layout.objectTypeSuperclass() property on the
Layout annotation.
Implicit Casts
IntToLong and
IntToDouble implicit cast flags can be set in the generated layout
by setting
Layout.implicitCastIntToLong() or
Layout.implicitCastIntToDouble(). This can only be
done in base layouts, not subclassed layouts.
Custom Identifiers
By default, internal
HiddenKey identifiers with descriptive names will be created for
your properties automatically. You can also specify the identifiers to use by defining them as
public static final fields in the interface.
@Layout
interface CustomIdentifierLayout {
public static final String A_IDENTIFIER = "A";
DynamicObject createCustomIdentifier(int a);
}