/*
* Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.runtime.objectstorage;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import sun.misc.Unsafe;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.nodes.NodeUtil.FieldOffsetProvider;
/**
* Maps names of instance variables to storage locations, which are either the offset of a primitive
* field in {@code RubyBasicObject}, or an index into an object array in {@code RubyBasicObject}.
* Object layouts are chained, with each having zero or one parents.
* <p>
* Object layouts are immutable, with the methods for adding new instance variables of generalizing
* the type of existing instance variables returning new object layouts.
*/
public class ObjectLayout {
public static final ObjectLayout EMPTY = new ObjectLayout();
private final ObjectLayout parent;
private final Map<String, StorageLocation> storageLocations = new HashMap<>();
private final int primitiveStorageLocationsUsed;
private final int objectStorageLocationsUsed;
public static final long FIRST_OFFSET = getFirstOffset();
private ObjectLayout() {
this.parent = null;
primitiveStorageLocationsUsed = 0;
objectStorageLocationsUsed = 0;
}
public ObjectLayout(ObjectLayout parent) {
this(parent, new HashMap<String, Class>());
}
public ObjectLayout(ObjectLayout parent, Map<String, Class> storageTypes) {
this.parent = parent;
// Start our offsets from where the parent ends
int primitiveStorageLocationIndex;
int objectStorageLocationIndex;
if (parent == null) {
primitiveStorageLocationIndex = 0;
objectStorageLocationIndex = 0;
} else {
primitiveStorageLocationIndex = parent.primitiveStorageLocationsUsed;
objectStorageLocationIndex = parent.objectStorageLocationsUsed;
}
// Go through the variables we've been asked to store
for (Entry<String, Class> entry : storageTypes.entrySet()) {
// TODO(cs): what if parent has it, but we need a more general type?
final String name = entry.getKey();
final Class type = entry.getValue();
if (parent == null || parent.findStorageLocation(name) == null) {
boolean canStoreInPrimitive = false;
int primitivesNeeded = 0;
if (type == Integer.class) {
canStoreInPrimitive = true;
primitivesNeeded = 1;
} else if (type == Long.class) {
canStoreInPrimitive = true;
primitivesNeeded = 2;
} else if (type == Double.class) {
canStoreInPrimitive = true;
primitivesNeeded = 2;
}
if (canStoreInPrimitive && primitiveStorageLocationIndex + primitivesNeeded <= ObjectStorage.PRIMITIVE_STORAGE_LOCATIONS_COUNT) {
final long offset = FIRST_OFFSET + Unsafe.ARRAY_INT_INDEX_SCALE * primitiveStorageLocationIndex;
final int mask = 1 << primitiveStorageLocationIndex;
StorageLocation newStorageLocation = null;
if (type == Integer.class) {
newStorageLocation = new IntegerStorageLocation(this, offset, mask);
} else if (type == Long.class) {
newStorageLocation = new LongStorageLocation(this, offset, mask);
} else if (type == Double.class) {
newStorageLocation = new DoubleStorageLocation(this, offset, mask);
}
storageLocations.put(entry.getKey(), newStorageLocation);
primitiveStorageLocationIndex += primitivesNeeded;
} else {
final ObjectStorageLocation newStorageLocation = new ObjectStorageLocation(this, objectStorageLocationIndex);
storageLocations.put(entry.getKey(), newStorageLocation);
objectStorageLocationIndex++;
}
}
}
primitiveStorageLocationsUsed = primitiveStorageLocationIndex;
objectStorageLocationsUsed = objectStorageLocationIndex;
}
/**
* Create a new version of this layout, but with a different parent. The new parent probably
* comes from the same Ruby class as it did, but it's a new layout because layouts are
* immutable, so modifications to the superclass yields a new layout.
*/
public ObjectLayout withNewParent(ObjectLayout newParent) {
return new ObjectLayout(newParent, getStorageTypes());
}
/**
* Create a new version of this layout but with a new variable.
*/
public ObjectLayout withNewVariable(String name, Class type) {
final Map<String, Class> storageTypes = getStorageTypes();
storageTypes.put(name, type);
return new ObjectLayout(parent, storageTypes);
}
/**
* Create a new version of this layout but with an existing variable generalized to support any
* type.
*/
public ObjectLayout withGeneralisedVariable(String name) {
return withNewVariable(name, Object.class);
}
/**
* Get a map of instance variable names to the type that they store.
*/
public Map<String, Class> getStorageTypes() {
Map<String, Class> storageTypes = new HashMap<>();
for (Entry<String, StorageLocation> entry : storageLocations.entrySet()) {
final String name = entry.getKey();
final StorageLocation storageLocation = entry.getValue();
if (storageLocation.getStoredClass() != null) {
storageTypes.put(name, storageLocation.getStoredClass());
}
}
return storageTypes;
}
/**
* Get a map of instance variable names to the type that they store, but including both this
* layout and all parent layouts.
*/
public Map<String, StorageLocation> getAllStorageLocations() {
final Map<String, StorageLocation> allStorageLocations = new HashMap<>();
allStorageLocations.putAll(storageLocations);
if (parent != null) {
allStorageLocations.putAll(parent.getAllStorageLocations());
}
return allStorageLocations;
}
/**
* Find a storage location from a name, including in parents.
*/
public StorageLocation findStorageLocation(String name) {
final StorageLocation storageLocation = storageLocations.get(name);
if (storageLocation != null) {
return storageLocation;
}
if (parent == null) {
return null;
}
return parent.findStorageLocation(name);
}
public int getObjectStorageLocationsUsed() {
return objectStorageLocationsUsed;
}
/**
* Does this layout include another layout? That is, is that other layout somewhere in the chain
* of parents? We say 'include' because all of the variables in a parent layout are available in
* your layout as well.
*/
public boolean contains(ObjectLayout other) {
ObjectLayout layout = this;
do {
if (other == layout) {
return true;
}
layout = layout.parent;
} while (layout != null);
return false;
}
private static long getFirstOffset() {
try {
final Field fieldOffsetProviderField = NodeUtil.class.getDeclaredField("unsafeFieldOffsetProvider");
fieldOffsetProviderField.setAccessible(true);
final FieldOffsetProvider fieldOffsetProvider = (FieldOffsetProvider) fieldOffsetProviderField.get(null);
final Field firstPrimitiveField = ObjectStorage.class.getDeclaredField("primitiveStorageLocation01");
return fieldOffsetProvider.objectFieldOffset(firstPrimitiveField);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}