org.jruby.runtime.ivars.VariableTableManager Maven / Gradle / Ivy
/*
***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.runtime.ivars;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jruby.Ruby;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.runtime.ObjectSpace;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ArraySupport;
import org.jruby.util.cli.Options;
import org.jruby.util.unsafe.UnsafeHolder;
import static org.jruby.util.StringSupport.EMPTY_STRING_ARRAY;
/**
* This class encapculates all logic relating to the management of instance
* variable tables in RubyBasicObject instances.
*
* The logic originally lived in both RubyBasicObject and RubyClass, tightly
* coupled to each and difficult to follow as it bounced back and forth. We
* moved the logic here for a couple reasons:
*
* To have a single place from which we could follow ivar logic.
* To make it easier to swap in new implementations of instance variable
* logic as we work toward reifying ivars into fields.
* To remove rather noisy logic from RubyBasicObject and RubyClass.
*/
public class VariableTableManager {
/** the "real" class associated with this table */
private final RubyClass realClass;
/** a map from strings to accessors for this table */
@SuppressWarnings("unchecked")
private Map variableAccessors = Collections.EMPTY_MAP;
/** an array of all registered variable names */
private volatile String[] variableNames = EMPTY_STRING_ARRAY;
/** whether a slot has been allocated to object_id */
private volatile boolean hasObjectID = false;
/** whether objects associated with this table use fields */
private volatile int fieldVariables = 0;
/** a lazy accessor for object_id */
private final VariableAccessorField objectIdVariableAccessorField = new VariableAccessorField("object_id");
/** a lazy accessor for FFI handle */
private final VariableAccessorField ffiHandleVariableAccessorField = new VariableAccessorField("ffi");
/** a lazy accessor for object group */
private final VariableAccessorField objectGroupVariableAccessorField = new VariableAccessorField("objectspace_group");
/**
* Construct a new VariableTable Manager for the given "real" class.
*
* @param realClass the "real" class associated with this table
*/
public VariableTableManager(RubyClass realClass) {
this.realClass = realClass;
}
/**
* Get the map of all current variable accessors with intent to read from it.
*
* @return a map of current variable accessors
*/
public Map getVariableAccessorsForRead() {
return variableAccessors;
}
/**
* Whether this table has been used to allocate space for an object_id.
*
* @return true if object_id has been allocated; false otherwise
*/
public boolean hasObjectID() {
return hasObjectID;
}
/**
* Get the object_id from a given RubyBasicObject, possibly allocating
* space for it.
*
* @param self the object from which to get object_id
* @return the object's object_id (possibly new)
*/
public long getObjectId(RubyBasicObject self) {
VariableAccessor objectIdAccessor = getObjectIdAccessorField().getVariableAccessorForRead();
Long id = (Long)objectIdAccessor.get(self);
if (id != null) return id;
synchronized (self) {
objectIdAccessor = getObjectIdAccessorField().getVariableAccessorForRead();
id = (Long)objectIdAccessor.get(self);
if (id != null) return id;
return initObjectId(self, getObjectIdAccessorField().getVariableAccessorForWrite(this));
}
}
/**
* Virtual entry point for setting a variable into an object.
*
* @param self the object into which to set the value
* @param index the index allocated for the value
* @param value the value
*/
public void setVariableInternal(RubyBasicObject self, int index, Object value) {
if(UnsafeHolder.U == null) {
SynchronizedVariableAccessor.setVariable(self,realClass,index,value);
} else {
StampedVariableAccessor.setVariable(self,realClass,index,value);
}
}
/**
* Static entry point for setting a variable in an object.
*
* @param realClass the "real" class of the object
* @param self the object into which to set the variable
* @param index the index allocated for the variable
* @param value the value of the variable
*/
public static void setVariableInternal(RubyClass realClass, RubyBasicObject self, int index, Object value) {
if(UnsafeHolder.U == null) {
SynchronizedVariableAccessor.setVariable(self,realClass,index,value);
} else {
StampedVariableAccessor.setVariable(self,realClass,index,value);
}
}
/**
* Get the variable accessor for the given name with intent to use it for
* writing.
*
* @param name the name of the variable
* @return an accessor appropriate for writing
*/
public VariableAccessor getVariableAccessorForWrite(String name) {
VariableAccessor ivarAccessor = variableAccessors.get(name);
if (ivarAccessor == null) {
synchronized (realClass) {
Map myVariableAccessors = variableAccessors;
ivarAccessor = myVariableAccessors.get(name);
if (ivarAccessor == null) {
// allocate a new accessor and populate a new table
ivarAccessor = allocateVariableAccessor(name);
Map newVariableAccessors = new HashMap(myVariableAccessors.size() + 1);
newVariableAccessors.putAll(myVariableAccessors);
newVariableAccessors.put(name, ivarAccessor);
variableAccessors = newVariableAccessors;
}
}
}
return ivarAccessor;
}
public VariableAccessor getVariableAccessorForVar(String name, int index) {
VariableAccessor ivarAccessor = variableAccessors.get(name);
if (ivarAccessor == null) {
synchronized (realClass) {
Map myVariableAccessors = variableAccessors;
ivarAccessor = myVariableAccessors.get(name);
if (ivarAccessor == null) {
// allocate a new accessor and populate a new table
ivarAccessor = allocateVariableAccessorForVar(name, index);
Map newVariableAccessors = new HashMap(myVariableAccessors.size() + 1);
newVariableAccessors.putAll(myVariableAccessors);
newVariableAccessors.put(name, ivarAccessor);
variableAccessors = newVariableAccessors;
}
}
}
return ivarAccessor;
}
/**
* Get the variable accessor for the given name with intent to use it for
* reading.
*
* @param name the name of the variable
* @return an accessor appropriate for reading
*/
public VariableAccessor getVariableAccessorForRead(String name) {
VariableAccessor accessor = getVariableAccessorsForRead().get(name);
if (accessor == null) accessor = VariableAccessor.DUMMY_ACCESSOR;
return accessor;
}
/**
* Retrieve the lazy accessor (VariableAccessorField) for object_id.
*
* @return the lazy accessor for object_id
*/
public VariableAccessorField getObjectIdAccessorField() {
return objectIdVariableAccessorField;
}
/**
* Retrieve the lazy accessor (VariableAccessorField) for FFI handle.
*
* @return the lazy accessor for FFI handle
*/
public VariableAccessorField getFFIHandleAccessorField() {
return ffiHandleVariableAccessorField;
}
/**
* Retrieve the read accessor for FFI handle.
*
* @return the read accessor for FFI handle
*/
public VariableAccessor getFFIHandleAccessorForRead() {
return ffiHandleVariableAccessorField.getVariableAccessorForRead();
}
/**
* Retrieve the write accessor for FFI handle.
*
* @return the write accessor for FFI handle
*/
public VariableAccessor getFFIHandleAccessorForWrite() {
return ffiHandleVariableAccessorField.getVariableAccessorForWrite(this);
}
/**
* Retrieve the lazy accessor (VariableAccessorField) for object group.
*
* @return the lazy accessor for object group
*/
public VariableAccessorField getObjectGroupAccessorField() {
return objectGroupVariableAccessorField;
}
/**
* Retrieve the read accessor for object group.
*
* @return the read accessor for object group
*/
public VariableAccessor getObjectGroupAccessorForRead() {
return objectGroupVariableAccessorField.getVariableAccessorForRead();
}
/**
* Retrieve the write accessor for object group.
*
* @return the write accessor for object group
*/
public VariableAccessor getObjectGroupAccessorForWrite() {
return objectGroupVariableAccessorField.getVariableAccessorForWrite(this);
}
/**
* Retrieve the FFI ext handle for the given object.
*
* @param self the object
* @return the object's FFI handle
*/
public final Object getFFIHandle(RubyBasicObject self) {
return getFFIHandleAccessorForRead().get(self);
}
/**
* Set the FFI handle for the given object.
*
* @param self the object
* @param self the object's FFI handle
*/
public final void setFFIHandle(RubyBasicObject self, Object value) {
int index = getFFIHandleAccessorForWrite().getIndex();
setVariableInternal(realClass, self, index, value);
}
/**
* Get the size of the variable table, excluding extra vars (object_id,
* etc).
*
* @return the variable table's size, excluding extras
*/
public int getVariableTableSize() {
return variableAccessors.size();
}
/**
* Get the size of the variable table, including extra vars (object_etc,
* etc).
*
* @return
*/
public int getVariableTableSizeWithExtras() {
return variableNames.length;
}
/**
* Get a Map representing all variables registered in the variable table.
*
* @return a map of names to accessors for all variables
*/
public Map getVariableTableCopy() {
return new HashMap(getVariableAccessorsForRead());
}
/**
* Get an array of all the known instance variable names. The offset into
* the array indicates the offset of the variable's value in the per-object
* variable array.
*
* @return a copy of the array of known instance variable names
*/
public String[] getVariableNames() {
return variableNames.clone();
}
/**
* Sync one this object's variables with other's - this is used to make
* rbClone work correctly.
*
* @param self the object into which to sync variables
* @param other the object from which to sync variables
*/
public void syncVariables(RubyBasicObject self, IRubyObject other) {
RubyClass otherRealClass = other.getMetaClass().getRealClass();
boolean sameTable = otherRealClass == realClass;
if (sameTable && fieldVariables == 0) {
int idIndex = otherRealClass.getObjectIdAccessorField().getVariableAccessorForRead().getIndex();
Object[] otherVars = ((RubyBasicObject) other).varTable;
if(UnsafeHolder.U == null)
{
synchronized (self) {
self.varTable = makeSyncedTable(self.varTable, otherVars, idIndex);
}
} else {
for(;;) {
int oldStamp = self.varTableStamp;
// wait for read mode
if((oldStamp & 0x01) == 1)
continue;
// acquire exclusive write mode
if(!UnsafeHolder.U.compareAndSwapInt(self, RubyBasicObject.STAMP_OFFSET, oldStamp, ++oldStamp))
continue;
Object[] currentTable = (Object[]) UnsafeHolder.U.getObjectVolatile(self, RubyBasicObject.VAR_TABLE_OFFSET);
Object[] newTable = makeSyncedTable(currentTable,otherVars, idIndex);
UnsafeHolder.U.putOrderedObject(self, RubyBasicObject.VAR_TABLE_OFFSET, newTable);
// release write mode
self.varTableStamp = oldStamp+1;
break;
}
}
} else {
for (Map.Entry entry : otherRealClass.getVariableAccessorsForRead().entrySet()) {
VariableAccessor accessor = entry.getValue();
Object value = accessor.get(other);
if (value != null) {
if (sameTable) {
accessor.set(self, value);
} else {
realClass.getVariableAccessorForWrite(accessor.getName()).set(self, value);
}
}
}
}
}
/**
* Returns true if object has any variables, defined as:
*
* - instance variables
*
- class variables
*
- constants
*
- internal variables, such as those used when marshaling Ranges and Exceptions
*
* @return true if object has any variables, else false
*/
public boolean hasVariables(RubyBasicObject object) {
// we check both to exclude object_id
Object[] myVarTable;
return fieldVariables > 0 || getVariableTableSize() > 0 && (myVarTable = object.varTable) != null && myVarTable.length > 0;
}
public void serializeVariables(RubyBasicObject object, ObjectOutputStream oos) throws IOException {
if (object.varTable != null) {
Map accessors = getVariableAccessorsForRead();
oos.writeInt(accessors.size());
for (VariableAccessor accessor : accessors.values()) {
oos.writeUTF(RubyBasicObject.ERR_INSECURE_SET_INST_VAR);
oos.writeObject(accessor.get(object));
}
} else {
oos.writeInt(0);
}
}
public void deserializeVariables(RubyBasicObject object, ObjectInputStream ois) throws IOException, ClassNotFoundException {
int varCount = ois.readInt();
for (int i = 0; i < varCount; i++) {
String name = ois.readUTF();
Object value = ois.readObject();
getVariableAccessorForWrite(name).set(object, value);
}
}
public Object clearVariable(RubyBasicObject object, String name) {
synchronized(object) {
Object value = getVariableAccessorForRead(name).get(object);
getVariableAccessorForWrite(name).set(object, null);
return value;
}
}
/**
* We lazily stand up the object ID since it forces us to stand up
* per-object state for a given object. We also check for ObjectSpace here,
* and normally we do not register a given object ID into ObjectSpace due
* to the high cost associated with constructing the related weakref. Most
* uses of id/object_id will only ever need it to be a unique identifier,
* and the id2ref behavior provided by ObjectSpace is considered internal
* and not generally supported.
*
* Note that this method does not need to be synchronized, since it is
* only called from #getObjectId, which synchronizes against the target
* object. The accessor is already present, and variable accesses are
* thread-safe (albeit not atomic, which necessitates the synchronization
* in getObjectId).
*
* Synchronization here ends up being a bottleneck for every object
* created from the class that contains this VariableTableManager. See
* GH #1400.
*
* @param objectIdAccessor The variable accessor to use for storing the
* generated object ID
* @return The generated object ID
*/
private long initObjectId(RubyBasicObject self, VariableAccessor objectIdAccessor) {
Ruby runtime = self.getRuntime();
long id;
if (runtime.isObjectSpaceEnabled()) {
id = runtime.getObjectSpace().createAndRegisterObjectId(self);
} else {
id = ObjectSpace.calculateObjectId(self);
}
// we use a direct path here to avoid frozen checks
setObjectId(realClass, self, objectIdAccessor.getIndex(), id);
return id;
}
/**
* Update object_id with the given value.
*
* @param self the object into which to set object_id
* @param index the index allocated to store object_id
* @param value the value of object_id
*/
private static void setObjectId(RubyClass realClass, RubyBasicObject self, int index, long value) {
if (index < 0) return;
setVariableInternal(realClass, self, index, value);
}
/**
* Make a new variable table based on the values in a current and other
* (incoming) table, excluding object_id at specified index.
*
* @param currentTable the current table
* @param otherTable the other (incoming) table
* @param objectIdIdx the index of object_id to exclude
* @return a new table formed by combining the given tables
*/
private static Object[] makeSyncedTable(Object[] currentTable, Object[] otherTable, int objectIdIdx) {
if (currentTable == null || currentTable.length < otherTable.length) {
currentTable = otherTable.clone();
} else {
ArraySupport.copy(otherTable, currentTable, 0, otherTable.length);
}
// null out object ID so we don't share it
if (objectIdIdx >= 0 && objectIdIdx < currentTable.length) {
currentTable[objectIdIdx] = null;
}
return currentTable;
}
/**
* Allocate a new VariableAccessor for the named variable.
*
* @param name the name of the variable
* @return the new VariableAccessor
*/
synchronized final VariableAccessor allocateVariableAccessor(String name) {
int id = realClass.id;
final String[] myVariableNames = variableNames;
final int newIndex = myVariableNames.length;
VariableAccessor newVariableAccessor;
if (Options.VOLATILE_VARIABLES.load()) {
if (UnsafeHolder.U == null) {
newVariableAccessor = new SynchronizedVariableAccessor(realClass, name, newIndex, id);
} else {
newVariableAccessor = new StampedVariableAccessor(realClass, name, newIndex, id);
}
} else {
if (UnsafeHolder.U == null) {
newVariableAccessor = new NonvolatileVariableAccessor(realClass, name, newIndex, id);
} else {
// We still need safe updating of the vartable, so we fall back on sync here too.
newVariableAccessor = new StampedVariableAccessor(realClass, name, newIndex, id);
}
}
final String[] newVariableNames = new String[newIndex + 1];
ArraySupport.copy(myVariableNames, 0, newVariableNames, 0, newIndex);
newVariableNames[newIndex] = name;
variableNames = newVariableNames;
return newVariableAccessor;
}
synchronized final VariableAccessor allocateVariableAccessorForVar(String name, int index) {
int id = realClass.id;
final String[] myVariableNames = variableNames;
final int newIndex = myVariableNames.length;
fieldVariables += 1;
VariableAccessor newVariableAccessor;
switch (index) {
case 0:
newVariableAccessor = new VariableAccessorVar0(realClass, name, newIndex, id);
break;
case 1:
newVariableAccessor = new VariableAccessorVar1(realClass, name, newIndex, id);
break;
case 2:
newVariableAccessor = new VariableAccessorVar2(realClass, name, newIndex, id);
break;
case 3:
newVariableAccessor = new VariableAccessorVar3(realClass, name, newIndex, id);
break;
case 4:
newVariableAccessor = new VariableAccessorVar4(realClass, name, newIndex, id);
break;
case 5:
newVariableAccessor = new VariableAccessorVar5(realClass, name, newIndex, id);
break;
case 6:
newVariableAccessor = new VariableAccessorVar6(realClass, name, newIndex, id);
break;
case 7:
newVariableAccessor = new VariableAccessorVar7(realClass, name, newIndex, id);
break;
case 8:
newVariableAccessor = new VariableAccessorVar8(realClass, name, newIndex, id);
break;
case 9:
newVariableAccessor = new VariableAccessorVar9(realClass, name, newIndex, id);
break;
default:
throw new RuntimeException("unsupported var index in " + realClass + ": " + index);
}
final String[] newVariableNames = new String[newIndex + 1];
ArraySupport.copy(myVariableNames, 0, newVariableNames, 0, newIndex);
newVariableNames[newIndex] = name;
variableNames = newVariableNames;
return newVariableAccessor;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy