Skip to content

Commit 945c31e

Browse files
committed
Ensure class variables have at least acquire-release semantics
* Encapsulate accesses so all accesses use the proper memory barriers. * See https://preshing.com/20120913/acquire-and-release-semantics/ and https://preshing.com/20130823/the-synchronizes-with-relation/ for good articles about this, and why those barriers should be enough.
1 parent 1c760e2 commit 945c31e

File tree

5 files changed

+51
-35
lines changed

5 files changed

+51
-35
lines changed

src/main/java/org/truffleruby/core/module/ModuleFields.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,9 @@ public void initCopy(RubyModule from) {
197197
}
198198

199199
for (Object key : fromFields.classVariables.getShape().getKeys()) {
200-
final Object value = DynamicObjectLibrary.getUncached().getOrDefault(fromFields.classVariables, key, null);
200+
final Object value = fromFields.classVariables.read((String) key, DynamicObjectLibrary.getUncached());
201201
if (value != null) { // do not copy if it was removed concurrently
202-
synchronized (this.classVariables) {
203-
DynamicObjectLibrary.getUncached().put(this.classVariables, key, value);
204-
}
202+
this.classVariables.put((String) key, value, DynamicObjectLibrary.getUncached());
205203
}
206204
}
207205

@@ -862,7 +860,7 @@ public void getAdjacentObjects(Set<Object> adjacent) {
862860
}
863861

864862
for (Object key : classVariables.getShape().getKeys()) {
865-
final Object value = DynamicObjectLibrary.getUncached().getOrDefault(classVariables, key, null);
863+
final Object value = classVariables.read((String) key, DynamicObjectLibrary.getUncached());
866864
if (value != null) {
867865
ObjectGraph.addProperty(adjacent, value);
868866
}

src/main/java/org/truffleruby/core/module/ModuleOperations.java

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -628,9 +628,7 @@ public static void setClassVariable(RubyLanguage language, RubyContext context,
628628
if (!trySetClassVariable(module, name, value)) {
629629
synchronized (context.getClassVariableDefinitionLock()) {
630630
if (!trySetClassVariable(module, name, value)) {
631-
synchronized (moduleFields.getClassVariables()) {
632-
DynamicObjectLibrary.getUncached().put(moduleFields.getClassVariables(), name, value);
633-
}
631+
moduleFields.getClassVariables().put(name, value, DynamicObjectLibrary.getUncached());
634632
}
635633
}
636634
}
@@ -639,30 +637,18 @@ public static void setClassVariable(RubyLanguage language, RubyContext context,
639637
private static boolean trySetClassVariable(RubyModule topModule, String name, Object value) {
640638
return classVariableLookup(
641639
topModule,
642-
module -> {
643-
synchronized (module.fields.getClassVariables()) {
644-
return DynamicObjectLibrary.getUncached().putIfPresent(
645-
module.fields.getClassVariables(),
646-
name,
647-
value) ? module : null;
648-
}
649-
}) != null;
640+
module -> module.fields.getClassVariables().putIfPresent(
641+
name,
642+
value,
643+
DynamicObjectLibrary.getUncached()) ? module : null) != null;
650644
}
651645

652646
@TruffleBoundary
653647
public static Object removeClassVariable(ModuleFields fields, RubyContext context, Node currentNode, String name) {
654648
fields.checkFrozen(context, currentNode);
655649

656650
final ClassVariableStorage classVariables = fields.getClassVariables();
657-
658-
final Object found;
659-
synchronized (classVariables) {
660-
found = DynamicObjectLibrary.getUncached().getOrDefault(classVariables, name, null);
661-
if (found != null) {
662-
DynamicObjectLibrary.getUncached().removeKey(classVariables, name);
663-
}
664-
}
665-
651+
final Object found = classVariables.remove(name, DynamicObjectLibrary.getUncached());
666652
if (found == null) {
667653
throw new RaiseException(
668654
context,

src/main/java/org/truffleruby/language/objects/classvariables/ClassVariableStorage.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
package org.truffleruby.language.objects.classvariables;
1111

1212
import com.oracle.truffle.api.object.DynamicObject;
13+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
1314
import org.truffleruby.RubyLanguage;
15+
import org.truffleruby.extra.ffi.Pointer;
1416

1517
public class ClassVariableStorage extends DynamicObject {
1618

@@ -27,4 +29,40 @@ public ClassVariableStorage(RubyLanguage language) {
2729
super(language.classVariableShape);
2830
}
2931

32+
public Object read(String name, DynamicObjectLibrary objectLibrary) {
33+
final Object value = objectLibrary.getOrDefault(this, name, null);
34+
if (objectLibrary.isShared(this)) {
35+
// This extra fence is to ensure acquire-release semantics for class variables, so the read above behaves
36+
// like a load-acquire. There is a corresponding store-release barrier for class variables writes.
37+
Pointer.UNSAFE.loadFence(); // load-acquire
38+
}
39+
return value;
40+
}
41+
42+
public void put(String name, Object value, DynamicObjectLibrary objectLibrary) {
43+
Pointer.UNSAFE.storeFence(); // store-release
44+
synchronized (this) {
45+
objectLibrary.put(this, name, value);
46+
}
47+
}
48+
49+
public boolean putIfPresent(String name, Object value, DynamicObjectLibrary objectLibrary) {
50+
Pointer.UNSAFE.storeFence(); // store-release
51+
synchronized (this) {
52+
return objectLibrary.putIfPresent(this, name, value);
53+
}
54+
}
55+
56+
public Object remove(String name, DynamicObjectLibrary objectLibrary) {
57+
final Object prev;
58+
synchronized (this) {
59+
prev = read(name, objectLibrary);
60+
if (prev != null) {
61+
Pointer.UNSAFE.storeFence(); // store-release
62+
objectLibrary.removeKey(this, name);
63+
}
64+
}
65+
return prev;
66+
}
67+
3068
}

src/main/java/org/truffleruby/language/objects/classvariables/LookupClassVariableNode.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ protected Object lookupClassVariable(RubyModule module, String name,
3131
@Cached LookupClassVariableStorageNode lookupClassVariableStorageNode,
3232
@Cached ConditionProfile noStorageProfile,
3333
@CachedLibrary(limit = "getDynamicObjectCacheLimit()") DynamicObjectLibrary objectLibrary) {
34-
final ClassVariableStorage objectForClassVariables = lookupClassVariableStorageNode.execute(module, name);
34+
final ClassVariableStorage classVariables = lookupClassVariableStorageNode.execute(module, name);
3535

36-
if (noStorageProfile.profile(objectForClassVariables == null)) {
36+
if (noStorageProfile.profile(classVariables == null)) {
3737
return null;
3838
} else {
39-
return objectLibrary.getOrDefault(objectForClassVariables, name, null);
39+
return classVariables.read(name, objectLibrary);
4040
}
4141
}
4242

src/main/java/org/truffleruby/language/objects/classvariables/SetClassVariableNode.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import com.oracle.truffle.api.profiles.BranchProfile;
1818
import org.truffleruby.core.module.ModuleOperations;
1919
import org.truffleruby.core.module.RubyModule;
20-
import org.truffleruby.extra.ffi.Pointer;
2120
import org.truffleruby.language.RubyContextNode;
2221
import org.truffleruby.language.objects.shared.WriteBarrierNode;
2322

@@ -49,13 +48,8 @@ protected Object setClassVariableShared(RubyModule module, String name, Object v
4948
@Cached BranchProfile slowPath) {
5049
// See WriteObjectFieldNode
5150
writeBarrierNode.executeWriteBarrier(value);
52-
Pointer.UNSAFE.storeFence();
53-
54-
final boolean set;
55-
synchronized (classVariableStorage) {
56-
set = objectLibrary.putIfPresent(classVariableStorage, name, value);
57-
}
5851

52+
final boolean set = classVariableStorage.putIfPresent(name, value, objectLibrary);
5953
if (!set) {
6054
slowPath.enter();
6155
ModuleOperations.setClassVariable(getLanguage(), getContext(), module, name, value, this);

0 commit comments

Comments
 (0)