Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b843739
Move existing class variable nodes into a package
chrisseaton Feb 14, 2021
7821651
Factor out ResolveTargetModuleForClassVariablesNode
chrisseaton Feb 14, 2021
ddfcab4
Factor out LookupClassVariableNode
chrisseaton Feb 14, 2021
d576e25
Isolate logic for resolveTargetModuleForClassVariables
chrisseaton Feb 14, 2021
246a54f
Don't need @Cached("create()")
chrisseaton Feb 14, 2021
beec5aa
Factor out SetClassVariableNode
chrisseaton Feb 14, 2021
1135c05
Remove some boundaries in class variable metaprogramming
chrisseaton Feb 14, 2021
52d419e
Factor out CheckClassVariableNameNode
chrisseaton Feb 14, 2021
7257bb8
Formatting
chrisseaton Feb 14, 2021
3d905a1
Fix bad renaming
chrisseaton Feb 14, 2021
619870e
Cache ResolveTargetModuleForClassVariablesNode
chrisseaton Feb 14, 2021
e9e5523
Pull out some of the abstraction in classVariableLookup
chrisseaton Feb 14, 2021
db89ab9
In LookupClassVariableNode, separate looking up the right storage and…
chrisseaton Feb 14, 2021
3d93110
Factor out LookupClassVariableStorageNode
chrisseaton Feb 14, 2021
0fa259f
Switch to using a DynamicObject for class variable storage
chrisseaton Feb 14, 2021
edc192d
Use an inline cache for reading from class variable storage when it's…
chrisseaton Feb 14, 2021
9c2adde
Use execute methods not call specialisations directly
chrisseaton Feb 14, 2021
ef9d44d
Fix bug in CheckClassVariableNameNode guard
chrisseaton Feb 15, 2021
99e4661
Fix deprecation
chrisseaton Feb 15, 2021
36bd41f
Primitives in ClassVariableStorage
chrisseaton Feb 15, 2021
45555d2
lookupClassVariable only needs to look in the shape
chrisseaton Feb 15, 2021
c3ec258
Tidy up
chrisseaton Feb 15, 2021
e7e5ae2
Cache a common case in LookupClassVariableStorageNode
chrisseaton Feb 15, 2021
d7dc8db
Cache a common case in SetClassVariableNode
chrisseaton Feb 15, 2021
c5dbda8
Tidy up
chrisseaton Feb 15, 2021
75f1f2b
Change log entry
chrisseaton Feb 16, 2021
4648df1
Fix-up
chrisseaton Feb 16, 2021
0195592
Use DynamicObjectLibrary in LookupClassVariableStorageNode
chrisseaton Feb 16, 2021
ff467bd
Use DynamicObjectLibrary SetClassVariableNode
chrisseaton Feb 16, 2021
5420eff
Simplify trySetClassVariable using putIfPresent
chrisseaton Feb 16, 2021
b34d04d
Add the whole ClassVariableStorage instance as an adjacent object to …
chrisseaton Feb 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Performance:
* Improve the performance of checks for recursion (#2189, @LillianZ).
* Improve random number generation performance by avoiding synchronization (#2190, @ivoanjo).
* We now create a single call target per block by default instead of two.
* Some uses of class variables are now much better optimized (#2259, @chrisseaton).

Changes:

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/truffleruby/RubyLanguage.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import org.truffleruby.language.RubyInlineParsingRequestNode;
import org.truffleruby.language.RubyParsingRequestNode;
import org.truffleruby.language.objects.RubyObjectType;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
import org.truffleruby.options.LanguageOptions;
import org.truffleruby.platform.Platform;
import org.truffleruby.shared.Metrics;
Expand Down Expand Up @@ -211,6 +212,8 @@ public final class RubyLanguage extends TruffleLanguage<RubyContext> {
public final Shape unboundMethodShape = createShape(RubyUnboundMethod.class);
public final Shape weakMapShape = createShape(RubyWeakMap.class);

public final Shape classVariableShape = Shape.newBuilder().layout(ClassVariableStorage.class).build();

public RubyLanguage() {
coreMethodAssumptions = new CoreMethodAssumptions(this);
coreStrings = new CoreStrings(this);
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/org/truffleruby/core/module/ModuleFields.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;

import com.oracle.truffle.api.object.DynamicObjectLibrary;
import org.jcodings.specific.UTF8Encoding;
import org.truffleruby.RubyContext;
import org.truffleruby.RubyLanguage;
Expand All @@ -46,6 +47,7 @@
import org.truffleruby.language.methods.InternalMethod;
import org.truffleruby.language.objects.ObjectGraph;
import org.truffleruby.language.objects.ObjectGraphNode;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
import org.truffleruby.language.objects.shared.SharedObjects;

import com.oracle.truffle.api.Assumption;
Expand Down Expand Up @@ -95,7 +97,7 @@ public static void debugModuleChain(RubyModule module) {

private final ConcurrentMap<String, InternalMethod> methods = new ConcurrentHashMap<>();
private final ConcurrentMap<String, RubyConstant> constants = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> classVariables = new ConcurrentHashMap<>();
private final ClassVariableStorage classVariables;

/** The refinements (calls to Module#refine) nested under/contained in this namespace module (M). Represented as a
* map of refined classes and modules (C) to refinement modules (R). */
Expand All @@ -121,6 +123,7 @@ public ModuleFields(
this.rubyModule = rubyModule;
this.methodsUnmodifiedAssumption = new CyclicAssumption("methods are unmodified");
this.constantsUnmodifiedAssumption = new CyclicAssumption("constants are unmodified");
classVariables = new ClassVariableStorage(language);
start = new PrependMarker(this);
}

Expand Down Expand Up @@ -193,7 +196,12 @@ public void initCopy(RubyModule from) {
this.constants.put(entry.getKey(), entry.getValue());
}

this.classVariables.putAll(fromFields.classVariables);
for (Object key : fromFields.classVariables.getShape().getKeys()) {
DynamicObjectLibrary.getUncached().put(
this.classVariables,
key,
DynamicObjectLibrary.getUncached().getOrDefault(fromFields.classVariables, key, null));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@woess Isn't there a more direct way to do a (shallow) copy of a DynamicObject?
There used to be DynamicObject#copy, but I see nothing similar in DynamicObjectLibrary.


if (fromFields.hasPrependedModules()) {
// Then the parent is the first in the prepend chain
Expand Down Expand Up @@ -745,7 +753,7 @@ public InternalMethod getMethod(String name) {
return methods.get(name);
}

public ConcurrentMap<String, Object> getClassVariables() {
public ClassVariableStorage getClassVariables() {
return classVariables;
}

Expand Down Expand Up @@ -849,9 +857,7 @@ public void getAdjacentObjects(Set<Object> adjacent) {
ObjectGraph.addProperty(adjacent, constant);
}

for (Object value : classVariables.values()) {
ObjectGraph.addProperty(adjacent, value);
}
ObjectGraph.addProperty(adjacent, classVariables);

for (InternalMethod method : methods.values()) {
ObjectGraph.addProperty(adjacent, method);
Expand Down
68 changes: 42 additions & 26 deletions src/main/java/org/truffleruby/core/module/ModuleNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.concurrent.ConcurrentMap;

import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jcodings.specific.UTF8Encoding;
import org.truffleruby.RubyContext;
import org.truffleruby.RubyLanguage;
Expand Down Expand Up @@ -60,7 +62,6 @@
import org.truffleruby.core.string.StringUtils;
import org.truffleruby.core.support.TypeNodes;
import org.truffleruby.core.symbol.RubySymbol;
import org.truffleruby.core.symbol.SymbolTable;
import org.truffleruby.language.LexicalScope;
import org.truffleruby.language.Nil;
import org.truffleruby.language.NotProvided;
Expand Down Expand Up @@ -103,6 +104,10 @@
import org.truffleruby.language.objects.ReadInstanceVariableNode;
import org.truffleruby.language.objects.SingletonClassNode;
import org.truffleruby.language.objects.WriteInstanceVariableNode;
import org.truffleruby.language.objects.classvariables.CheckClassVariableNameNode;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
import org.truffleruby.language.objects.classvariables.LookupClassVariableNode;
import org.truffleruby.language.objects.classvariables.SetClassVariableNode;
import org.truffleruby.language.yield.CallBlockNode;
import org.truffleruby.parser.Identifiers;
import org.truffleruby.parser.ParserContext;
Expand Down Expand Up @@ -804,14 +809,12 @@ protected RubyNode coerceToString(RubyNode name) {
return NameToJavaStringNode.create(name);
}

@TruffleBoundary
@Specialization
protected boolean isClassVariableDefinedString(RubyModule module, String name) {
SymbolTable.checkClassVariableName(getContext(), name, module, this);

final Object value = ModuleOperations.lookupClassVariable(module, name);

return value != null;
protected boolean isClassVariableDefinedString(RubyModule module, String name,
@Cached CheckClassVariableNameNode checkClassVariableNameNode,
@Cached LookupClassVariableNode lookupClassVariableNode) {
checkClassVariableNameNode.execute(module, name);
return lookupClassVariableNode.execute(module, name) != null;
}

}
Expand All @@ -827,13 +830,14 @@ protected RubyNode coerceToString(RubyNode name) {
}

@Specialization
@TruffleBoundary
protected Object getClassVariable(RubyModule module, String name) {
SymbolTable.checkClassVariableName(getContext(), name, module, this);
protected Object getClassVariable(RubyModule module, String name,
@Cached CheckClassVariableNameNode checkClassVariableNameNode,
@Cached LookupClassVariableNode lookupClassVariableNode,
@Cached ConditionProfile undefinedProfile) {
checkClassVariableNameNode.execute(module, name);
final Object value = lookupClassVariableNode.execute(module, name);

final Object value = ModuleOperations.lookupClassVariable(module, name);

if (value == null) {
if (undefinedProfile.profile(value == null)) {
throw new RaiseException(
getContext(),
coreExceptions().nameErrorUninitializedClassVariable(module, name, this));
Expand All @@ -856,12 +860,11 @@ protected RubyNode coerceToString(RubyNode name) {
}

@Specialization
@TruffleBoundary
protected Object setClassVariable(RubyModule module, String name, Object value) {
SymbolTable.checkClassVariableName(getContext(), name, module, this);

ModuleOperations.setClassVariable(getLanguage(), getContext(), module, name, value, this);

protected Object setClassVariable(RubyModule module, String name, Object value,
@Cached CheckClassVariableNameNode checkClassVariableNameNode,
@Cached SetClassVariableNode setClassVariableNode) {
checkClassVariableNameNode.execute(module, name);
setClassVariableNode.execute(module, name, value);
return value;
}

Expand All @@ -873,12 +876,25 @@ public abstract static class ClassVariablesNode extends CoreMethodArrayArguments
@TruffleBoundary
@Specialization
protected RubyArray getClassVariables(RubyModule module) {
final Map<String, Object> allClassVariables = ModuleOperations.getAllClassVariables(module);
final int size = allClassVariables.size();
final Map<String, Object> classVariables = new HashMap<>();

ModuleOperations.classVariableLookup(module, m -> {
final ClassVariableStorage classVariableStorage = m.fields.getClassVariables();

for (Object key : m.fields.getClassVariables().getShape().getKeys()) {
classVariables.put(
(String) key,
DynamicObjectLibrary.getUncached().getOrDefault(classVariableStorage, key, null));
}

return null;
});

final int size = classVariables.size();
final Object[] store = new Object[size];

int i = 0;
for (String variable : allClassVariables.keySet()) {
for (String variable : classVariables.keySet()) {
store[i++] = getSymbol(variable);
}
return createArray(store);
Expand Down Expand Up @@ -1941,10 +1957,10 @@ protected RubyNode coerceToString(RubyNode name) {
return NameToJavaStringNode.create(name);
}

@TruffleBoundary
@Specialization
protected Object removeClassVariableString(RubyModule module, String name) {
SymbolTable.checkClassVariableName(getContext(), name, module, this);
protected Object removeClassVariableString(RubyModule module, String name,
@Cached CheckClassVariableNameNode checkClassVariableNameNode) {
checkClassVariableNameNode.execute(module, name);
return ModuleOperations.removeClassVariable(module.fields, getContext(), this, name);
}

Expand Down
52 changes: 17 additions & 35 deletions src/main/java/org/truffleruby/core/module/ModuleOperations.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Map.Entry;
import java.util.function.Function;

import com.oracle.truffle.api.object.DynamicObjectLibrary;
import org.truffleruby.RubyContext;
import org.truffleruby.RubyLanguage;
import org.truffleruby.collections.Memo;
Expand All @@ -27,6 +28,7 @@
import org.truffleruby.language.control.RaiseException;
import org.truffleruby.language.methods.DeclarationContext;
import org.truffleruby.language.methods.InternalMethod;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;
import org.truffleruby.language.objects.shared.SharedObjects;
import org.truffleruby.parser.Identifiers;

Expand Down Expand Up @@ -614,23 +616,6 @@ private static Assumption[] toArray(ArrayList<Assumption> assumptions) {
return assumptions.toArray(EMPTY_ASSUMPTION_ARRAY);
}

@TruffleBoundary
public static Map<String, Object> getAllClassVariables(RubyModule module) {
final Map<String, Object> classVariables = new HashMap<>();

classVariableLookup(module, module1 -> {
classVariables.putAll(module1.fields.getClassVariables());
return null;
});

return classVariables;
}

@TruffleBoundary
public static Object lookupClassVariable(RubyModule module, final String name) {
return classVariableLookup(module, module1 -> module1.fields.getClassVariables().get(name));
}

@TruffleBoundary
public static void setClassVariable(RubyLanguage language, RubyContext context, RubyModule module, String name,
Object value, Node currentNode) {
Expand All @@ -643,48 +628,45 @@ public static void setClassVariable(RubyLanguage language, RubyContext context,
if (!trySetClassVariable(module, name, value)) {
synchronized (context.getClassVariableDefinitionLock()) {
if (!trySetClassVariable(module, name, value)) {
/* This is double-checked locking, but it is safe because when writing to a ConcurrentHashMap "an
* update operation for a given key bears a happens-before relation with any (non-null) retrieval
* for that key reporting the updated value" (JavaDoc) so the value is guaranteed to be fully
* published before it can be found in the map. */

moduleFields.getClassVariables().put(name, value);
DynamicObjectLibrary.getUncached().put(moduleFields.getClassVariables(), name, value);
}
}
}
}

private static boolean trySetClassVariable(RubyModule topModule, String name, Object value) {
final RubyModule found = classVariableLookup(topModule, module -> {
final ModuleFields moduleFields = module.fields;
if (moduleFields.getClassVariables().replace(name, value) != null) {
return module;
} else {
return null;
}
});
return found != null;
return classVariableLookup(
topModule,
module -> DynamicObjectLibrary.getUncached().putIfPresent(
module.fields.getClassVariables(),
name,
value) ? module : null) != null;
}

@TruffleBoundary
public static Object removeClassVariable(ModuleFields moduleFields, RubyContext context, Node currentNode,
String name) {
moduleFields.checkFrozen(context, currentNode);

final Object found = moduleFields.getClassVariables().remove(name);
final ClassVariableStorage classVariableStorage = moduleFields.getClassVariables();

final Object found = DynamicObjectLibrary.getUncached().getOrDefault(classVariableStorage, name, null);

if (found == null) {
throw new RaiseException(
context,
context.getCoreExceptions().nameErrorClassVariableNotDefined(
name,
moduleFields.rubyModule,
currentNode));
} else {
DynamicObjectLibrary.getUncached().removeKey(classVariableStorage, name);
return found;
}
return found;
}

@TruffleBoundary
private static <R> R classVariableLookup(RubyModule module, Function<RubyModule, R> action) {
public static <R> R classVariableLookup(RubyModule module, Function<RubyModule, R> action) {
// Look in the current module
R result = action.apply(module);
if (result != null) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/truffleruby/core/module/RubyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.source.SourceSection;
import org.truffleruby.language.objects.classvariables.ClassVariableStorage;

@ExportLibrary(InteropLibrary.class)
public class RubyModule extends RubyDynamicObject implements ObjectGraphNode {
Expand Down Expand Up @@ -63,6 +64,10 @@ public String getName() {
return fields.getName();
}

public ClassVariableStorage getClassVariables() {
return fields.getClassVariables();
}

@TruffleBoundary
@Override
public String toString() {
Expand Down
13 changes: 0 additions & 13 deletions src/main/java/org/truffleruby/language/LexicalScope.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.truffleruby.core.module.RubyModule;

import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;

/** Instances of this class represent the Ruby lexical scope for constants, which is only changed by `class Name`,
* `module Name` and `class << expr`. Other lexical scope features such as refinement and the default definee are
Expand Down Expand Up @@ -48,18 +47,6 @@ public void unsafeSetLiveModule(RubyModule liveModule) {
this.liveModule = liveModule;
}

@TruffleBoundary
public static RubyModule resolveTargetModuleForClassVariables(LexicalScope lexicalScope) {
LexicalScope scope = lexicalScope;

// MRI logic: ignore lexical scopes (cref) referring to singleton classes
while (RubyGuards.isSingletonClass(scope.liveModule)) {
scope = scope.parent;
}

return scope.liveModule;
}

@Override
public String toString() {
return " :: " + liveModule + (parent == null ? "" : parent.toString());
Expand Down
Loading