com.oracle.truffle.api.staticobject.StaticShape Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api.staticobject;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleLanguage;
import org.graalvm.nativeimage.ImageInfo;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A StaticShape is an immutable descriptor of the layout of a static object and is a good entry
* point to learn about the Static Object Model. Here is an overview:
*
* - {@link StaticShape#newBuilder(TruffleLanguage)} returns a {@link StaticShape.Builder} object
* that can be used to {@linkplain StaticShape.Builder#property(StaticProperty, Class, boolean)
* register} {@linkplain StaticProperty static properties} and to generate a new static shape by
* calling one of its {@linkplain Builder#build() build methods}.
*
- {@link StaticShape#getFactory()} returns an implementation of the {@linkplain Builder#build()
* default} or the {@linkplain StaticShape.Builder#build(Class, Class) user-defined} factory
* interface that must be used to allocate static objects with the current shape.
*
- Property values stored in a static object of a given shape can be accessed by the
* {@link StaticProperty} instances registered to the builder that generated that shape or one of
* its {@linkplain StaticShape.Builder#build(StaticShape) parent shapes}. Note that static shapes do
* not store the list of {@linkplain StaticProperty static properties} associated to them. It is up
* to the user to store this information when required, for example in a class that contains
* references to the static shape and the list of {@linkplain StaticProperty static properties}.
*
*
*
* StaticShape cannot be subclassed by custom implementations and, when required, it allows
* {@linkplain StaticProperty static properties} to check that the receiver object matches the
* expected shape.
*
* @see StaticShape#newBuilder(TruffleLanguage)
* @see StaticShape.Builder
* @see StaticProperty
* @see DefaultStaticProperty
* @see DefaultStaticObjectFactory
* @param the {@linkplain Builder#build() default} or the
* {@linkplain StaticShape.Builder#build(Class, Class) user-defined} factory interface to
* allocate static objects
* @since 21.3.0
*/
public abstract class StaticShape {
enum StorageStrategy {
ARRAY_BASED,
FIELD_BASED,
POD_BASED
}
static final Unsafe UNSAFE = getUnsafe();
final Class> storageClass;
final boolean safetyChecks;
@CompilationFinal //
T factory;
StaticShape(Class> storageClass, boolean safetyChecks) {
this.storageClass = storageClass;
this.safetyChecks = safetyChecks;
}
/**
* Creates a new static shape builder.
*
* The builder instance is not thread-safe and must not be used from multiple threads at the
* same time. Users of the Static Object Model are expected to define custom subtypes of
* {@link StaticProperty} or use {@link DefaultStaticProperty}, a trivial default
* implementation. In both cases, static properties must be registered to a static shape builder
* using {@link StaticShape.Builder#property(StaticProperty, Class, boolean)}. Then, after
* allocating a {@link StaticShape} instance with one of the {@link StaticShape.Builder#build()}
* methods and allocating a static object using the factory class provided by
* {@link StaticShape#getFactory()}, users can call the accessor methods defined in
* {@link StaticProperty} to get and set property values stored in a static object instance.
*
* @param language an instance of the {@link TruffleLanguage} that uses the Static Object Model
* @return a new static shape builder
* @throws NullPointerException if language is null
*
* @see StaticShape
* @see StaticProperty
* @see DefaultStaticProperty
* @see DefaultStaticObjectFactory
* @since 21.3.0
*/
public static Builder newBuilder(TruffleLanguage> language) {
Objects.requireNonNull(language);
return new Builder(language);
}
final void setFactory(T factory) {
assert this.factory == null;
this.factory = factory;
}
/**
* Returns an instance of the {@linkplain Builder#build() default} or the
* {@linkplain StaticShape.Builder#build(Class, Class) user-defined} factory interface that must
* be used to allocate static objects with the current shape.
*
* @see StaticShape.Builder#build()
* @see StaticShape.Builder#build(StaticShape)
* @see StaticShape.Builder#build(Class, Class)
* @since 21.3.0
*/
public final T getFactory() {
return factory;
}
final Class> getStorageClass() {
return storageClass;
}
abstract Object getStorage(Object obj, boolean primitive);
final U cast(Object obj, Class type, boolean checkCondition) {
if (safetyChecks) {
return checkedCast(obj, type);
} else {
assert checkedCast(obj, type) != null;
return SomAccessor.RUNTIME.unsafeCast(obj, type, !checkCondition || type.isInstance(obj), false, false);
}
}
@SuppressWarnings("unchecked")
final Class getFactoryInterface() {
// Builder.validate() makes sure that the factory class implements a single interface
assert factory.getClass().getInterfaces().length == 1;
return (Class) factory.getClass().getInterfaces()[0];
}
private static U checkedCast(Object obj, Class type) {
try {
return type.cast(obj);
} catch (ClassCastException e) {
throw new IllegalArgumentException("Object '" + obj + "' of class '" + obj.getClass().getName() + "' does not have the expected shape", e);
}
}
private static Unsafe getUnsafe() {
try {
return Unsafe.getUnsafe();
} catch (SecurityException e) {
}
try {
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
} catch (Exception e) {
throw new RuntimeException("exception while trying to get Unsafe.theUnsafe via reflection:", e);
}
}
/**
* Builder class to construct {@link StaticShape} instances. The builder instance is not
* thread-safe and must not be used from multiple threads at the same time.
*
* @see StaticShape#newBuilder(TruffleLanguage)
* @since 21.3.0
*/
public static final class Builder {
private static final int MAX_NUMBER_OF_PROPERTIES = 65535;
private static final int MAX_PROPERTY_ID_BYTE_LENGTH = 65535;
private static final String DELIMITER = "$$";
private static final AtomicInteger counter = new AtomicInteger();
private final String storageClassName;
private final HashMap staticProperties = new LinkedHashMap<>();
private final TruffleLanguage> language;
boolean hasLongPropertyId = false;
boolean isActive = true;
Builder(TruffleLanguage> language) {
this.language = language;
storageClassName = storageClassName();
}
static String storageClassName() {
return ShapeGenerator.class.getPackage().getName().replace('.', '/') + "/GeneratedStaticObject" + DELIMITER + counter.incrementAndGet();
}
/**
* Adds a {@link StaticProperty} to the static shape to be constructed. The
* {@linkplain StaticProperty#getId() property id} cannot be null or an empty String. It is
* not allowed to add two {@linkplain StaticProperty properties} with the same
* {@linkplain StaticProperty#getId() id} to the same builder, or to add the same
* {@linkplain StaticProperty property} to more than one builder. Static shapes that
* {@linkplain StaticShape.Builder#build(StaticShape) extend a parent shape} can have
* {@linkplain StaticProperty properties} with the same {@linkplain StaticProperty#getId()
* id} of those in the parent shape.
*
* Only property accesses that match the specified type are allowed. Property values can be
* optionally stored in a final field. Accesses to such values might be specially optimized
* by the compiler. For example, reads might be constant-folded. It is up to the user to
* enforce that property values stored as final are not assigned more than once.
*
* @see DefaultStaticProperty
* @param property the {@link StaticProperty} to be added
* @param type the type of the {@link StaticProperty} to be added.
* @param storeAsFinal if this property value can be stored in a final field
* @return the Builder instance
* @throws IllegalArgumentException if more than 65535 properties are added, or if the
* {@linkplain StaticProperty#getId() property id} is an empty string or it is
* equal to the id of another static property already registered to this
* builder.
* @throws IllegalStateException if this method is invoked after building a static shape
* @throws NullPointerException if the {@linkplain StaticProperty#getId() property id} is
* null
* @since 21.3.0
*/
public Builder property(StaticProperty property, Class> type, boolean storeAsFinal) {
CompilerAsserts.neverPartOfCompilation();
StaticPropertyValidator.validate(type);
checkStatus();
property.init(type, storeAsFinal);
staticProperties.put(validateAndGetId(property), property);
return this;
}
/**
* Builds a new {@linkplain StaticShape static shape} using the configuration of this
* builder. The factory class returned by {@link StaticShape#getFactory()} implements
* {@link DefaultStaticObjectFactory} and static objects extend {@link Object}.
*
* @see DefaultStaticObjectFactory
* @see StaticShape.Builder#build(StaticShape)
* @see StaticShape.Builder#build(Class, Class)
* @return the new {@link StaticShape}
* @throws IllegalStateException if a static property was added to more than one builder or
* multiple times to the same builder, if this method is invoked more than once,
* or if one of the static property types is not visible to the class loader
* that loaded the default factory interface.
* @since 21.3.0
*/
public StaticShape build() {
return build(Object.class, DefaultStaticObjectFactory.class);
}
/**
* Builds a new {@linkplain StaticShape static shape} that extends the provided parent
* {@link StaticShape}. {@linkplain StaticProperty Static properties} of the parent shape
* can be used to access field values of static objects with the child shape. The factory
* class returned by {@link StaticShape#getFactory()} extends the one of the parent shape
* and static objects extend the static object class allocated by the factory class of the
* parent shape.
*
* @see StaticShape.Builder#build()
* @see StaticShape.Builder#build(Class, Class)
* @param parentShape the parent {@linkplain StaticShape shape}
* @param the generic type of the parent {@linkplain StaticShape shape}
* @return the new {@link StaticShape}
* @throws IllegalStateException if a static property was added to more than one builder or
* multiple times to the same builder, if this method is invoked more than once,
* or if one of the static property types is not visible to the class loader
* that loaded the default factory interface.
* @since 21.3.0
*/
public StaticShape build(StaticShape parentShape) {
Objects.requireNonNull(parentShape);
GeneratorClassLoader gcl = getOrCreateClassLoader(parentShape.getFactoryInterface());
ShapeGenerator sg = ShapeGenerator.getShapeGenerator(language, gcl, parentShape, getStorageStrategy(), storageClassName);
return build(sg, parentShape);
}
/**
* Builds a new {@linkplain StaticShape static shape} using the configuration of this
* builder. The factory class returned by {@link StaticShape#getFactory()} implements
* factoryInterface and static objects extend superClass.
*
*
* The following constraints are enforced:
*
* - factoryInterface must be an interface
*
- the arguments of every method in factoryInterface must match those of a visible
* constructor of superClass
*
- the return type of every method in factoryInterface must be assignable from the
* superClass
*
- superClass does not have abstract methods
*
- if superClass is {@link Cloneable}, it cannot override {@link Object#clone()} with a
* final method
*
*
* @see StaticShape.Builder#build()
* @see StaticShape.Builder#build(StaticShape)
* @param superClass the class that static objects must extend
* @param factoryInterface the factory interface that the factory class returned by
* {@link StaticShape#getFactory()} must implement
* @param the class of the factory interface
* @return the new {@link StaticShape}
* @throws IllegalArgumentException if factoryInterface is not an interface, if the
* arguments of a method in factoryInterface do not match those of a visible
* constructor in superClass, if the return type of a method in factoryInterface
* is not assignable from superClass, if {@link StaticShape} is not visible to
* the class loader of factoryInterface, if superClass has abstract methods or
* if superClass is {@link Cloneable} and overrides {@link Object#clone()} with
* a final method
* @throws IllegalStateException if a static property was added to more than one builder or
* multiple times to the same builder, if this method is invoked more than once,
* or if one of the static property types is not visible to the class loader
* that loaded the factory interface.
* @since 21.3.0
*/
public StaticShape build(Class> superClass, Class factoryInterface) {
validateClasses(superClass, factoryInterface);
GeneratorClassLoader gcl = getOrCreateClassLoader(factoryInterface);
ShapeGenerator sg = ShapeGenerator.getShapeGenerator(language, gcl, superClass, factoryInterface, getStorageStrategy(), storageClassName);
return build(sg, null);
}
private StaticShape build(ShapeGenerator sg, StaticShape parentShape) {
CompilerAsserts.neverPartOfCompilation();
checkStatus();
Map properties = hasLongPropertyId ? defaultPropertyIds(staticProperties) : staticProperties;
boolean safetyChecks = !SomAccessor.ENGINE.areStaticObjectSafetyChecksRelaxed(SomAccessor.LANGUAGE.getPolyglotLanguageInstance(language));
StaticShape shape = sg.generateShape(parentShape, properties, safetyChecks, storageClassName);
for (StaticProperty staticProperty : properties.values()) {
staticProperty.initShape(shape);
}
setInactive();
return shape;
}
private void checkStatus() {
if (!isActive) {
throw new IllegalStateException("This Builder instance has already built a StaticShape. It is not possible to add static properties or build other shapes");
}
}
private void setInactive() {
isActive = false;
}
private GeneratorClassLoader getOrCreateClassLoader(Class> referenceClass) {
ClassLoader cl = SomAccessor.ENGINE.getStaticObjectClassLoader(SomAccessor.LANGUAGE.getPolyglotLanguageInstance(language), referenceClass);
if (cl == null) {
cl = new GeneratorClassLoader(referenceClass);
SomAccessor.ENGINE.setStaticObjectClassLoader(SomAccessor.LANGUAGE.getPolyglotLanguageInstance(language), referenceClass, cl);
}
if (!GeneratorClassLoader.class.isInstance(cl)) {
throw new RuntimeException("The Truffle language instance associated to this Builder returned an unexpected class loader");
}
return (GeneratorClassLoader) cl;
}
private String validateAndGetId(StaticProperty property) {
String id = property.getId();
Objects.requireNonNull(id);
if (staticProperties.size() == MAX_NUMBER_OF_PROPERTIES) {
throw new IllegalArgumentException("This builder already contains the maximum number of properties: " + MAX_NUMBER_OF_PROPERTIES);
}
if (id.length() == 0) {
throw new IllegalArgumentException("The property id cannot be an empty string");
}
// escape chars that are forbidden for field names
id = id.replace("_", "__");
id = id.replace(".", "_,");
id = id.replace(";", "_:");
id = id.replace("[", "_]");
id = id.replace("/", "_\\");
if (staticProperties.containsKey(id)) {
throw new IllegalArgumentException("This builder already contains a property with id '" + id + "'");
}
if (modifiedUtfLength(id) > MAX_PROPERTY_ID_BYTE_LENGTH) {
hasLongPropertyId = true;
}
return id;
}
// Reflectively invoked also from TruffleBaseFeature.StaticObjectSupport
private static void validateClasses(Class> storageSuperClass, Class> storageFactoryInterface) {
CompilerAsserts.neverPartOfCompilation();
if (!storageFactoryInterface.isInterface()) {
throw new IllegalArgumentException(storageFactoryInterface.getName() + " must be an interface.");
}
// Since methods in the factory interface must have the storage super class as return
// type, calling `storageFactoryInterface.getMethods()` also verifies that the class
// loader of the factory interface can load the storage super class
for (Method m : storageFactoryInterface.getMethods()) {
// this also verifies that the class loader of the factory interface is the same or
// a child of the class loader of the storage super class
if (!m.getReturnType().isAssignableFrom(storageSuperClass)) {
throw new IllegalArgumentException("The return type of '" + m + "' is not assignable from '" + storageSuperClass.getName() + "'");
}
try {
storageSuperClass.getDeclaredConstructor(m.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Method '" + m + "' does not match any constructor in '" + storageSuperClass.getName() + "'", e);
}
}
// The array-based storage strategy stores the StaticShape in an extra field of the
// generated class. Therefore, the class loader that loads generated classes must have
// visibility of StaticShape.
if (!isClassVisible(storageFactoryInterface.getClassLoader(), StaticShape.class)) {
throw new IllegalArgumentException("The class loader of factory interface '" + storageFactoryInterface.getName() + "' (cl: '" + storageFactoryInterface.getClassLoader() +
"') must have visibility of '" + StaticShape.class.getName() + "' (cl: '" + StaticShape.class.getClassLoader() + "')");
}
for (Class> c = storageSuperClass; c != null; c = c.getSuperclass()) {
for (Method m : c.getDeclaredMethods()) {
if (Modifier.isAbstract(m.getModifiers())) {
throw new IllegalArgumentException("'" + storageSuperClass.getName() + "' has abstract methods");
}
}
}
if (Cloneable.class.isAssignableFrom(storageSuperClass)) {
Method clone = getCloneMethod(storageSuperClass);
if (clone != null && Modifier.isFinal(clone.getModifiers())) {
throw new IllegalArgumentException("'" + storageSuperClass.getName() + "' implements Cloneable and declares a final 'clone()' method");
}
}
}
private static boolean isClassVisible(ClassLoader cl, Class> clazz) {
if (cl == null) {
return clazz.getClassLoader() == null;
} else {
try {
cl.loadClass(clazz.getName());
return true;
} catch (ClassNotFoundException e) {
// Swallow the exception
return false;
}
}
}
private static Map defaultPropertyIds(Map staticProperties) {
Map newStaticProperties = new LinkedHashMap<>();
int idx = 0;
for (StaticProperty property : staticProperties.values()) {
newStaticProperties.put("field" + idx++, property);
}
return newStaticProperties;
}
private static int modifiedUtfLength(String str) {
int strlen = str.length();
int utflen = 0;
/* use charAt instead of copying String to char array */
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
return utflen;
}
private static Method getCloneMethod(Class> c) {
for (Class> clazz = c; clazz != null; clazz = clazz.getSuperclass()) {
try {
return clazz.getDeclaredMethod("clone");
} catch (NoSuchMethodException e) {
// Swallow the error, check the super class
}
}
return null;
}
private StorageStrategy getStorageStrategy() {
String strategy = SomAccessor.ENGINE.getStaticObjectStorageStrategy(SomAccessor.LANGUAGE.getPolyglotLanguageInstance(language));
switch (strategy) {
case "DEFAULT":
if (ImageInfo.inImageCode()) {
return StorageStrategy.ARRAY_BASED;
} else {
return StorageStrategy.FIELD_BASED;
}
case "FIELD_BASED":
if (ImageInfo.inImageCode()) {
return StorageStrategy.POD_BASED;
} else {
return StorageStrategy.FIELD_BASED;
}
case "ARRAY_BASED":
return StorageStrategy.ARRAY_BASED;
default:
throw new IllegalArgumentException("Should not reach here. Unexpected storage strategy: " + strategy);
}
}
}
}