Skip to content

Commit 1f70f91

Browse files
committed
[GR-30892] Implement shared handle maps for immutable ruby objects as VALUEs.
PullRequest: truffleruby/2747
2 parents e840004 + c1c71ca commit 1f70f91

File tree

20 files changed

+281
-120
lines changed

20 files changed

+281
-120
lines changed

src/main/java/org/truffleruby/RubyContext.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ public RubyContext(RubyLanguage language, TruffleLanguage.Env env) {
183183
codeLoader = new CodeLoader(language, this);
184184
featureLoader = new FeatureLoader(this, language);
185185
referenceProcessor = new ReferenceProcessor(this);
186-
finalizationService = new FinalizationService(this, referenceProcessor);
187-
markingService = new MarkingService(this, referenceProcessor);
186+
finalizationService = new FinalizationService(referenceProcessor);
187+
markingService = new MarkingService(referenceProcessor);
188188

189189
// We need to construct this at runtime
190190
random = createRandomInstance();
@@ -528,6 +528,7 @@ private void dispose() {
528528
RubyLanguage.LOGGER.info(
529529
"Total VALUE object to native conversions: " + getValueWrapperManager().totalHandleAllocations());
530530
}
531+
valueWrapperManager.freeAllBlocksInMap(language);
531532
}
532533

533534
public boolean isPreInitializing() {

src/main/java/org/truffleruby/RubyLanguage.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.io.File;
1313
import java.io.IOException;
14+
import java.lang.ref.ReferenceQueue;
1415
import java.util.Arrays;
1516
import java.util.Objects;
1617
import java.util.Optional;
@@ -27,8 +28,10 @@
2728
import org.graalvm.options.OptionDescriptors;
2829
import org.jcodings.Encoding;
2930
import org.truffleruby.builtins.PrimitiveManager;
31+
import org.truffleruby.cext.ValueWrapperManager;
3032
import org.truffleruby.collections.SharedIndicesMap;
3133
import org.truffleruby.collections.SharedIndicesMap.LanguageArray;
34+
import org.truffleruby.core.FinalizationService;
3235
import org.truffleruby.core.RubyHandle;
3336
import org.truffleruby.core.array.RubyArray;
3437
import org.truffleruby.core.basicobject.RubyBasicObject;
@@ -186,6 +189,12 @@ public final class RubyLanguage extends TruffleLanguage<RubyContext> {
186189
public final SymbolTable symbolTable;
187190
public final FrozenStringLiterals frozenStringLiterals;
188191
public final Encodings encodings;
192+
193+
public final ReferenceQueue<Object> sharedReferenceQueue = new ReferenceQueue<>();
194+
public final FinalizationService sharedFinzationService = new FinalizationService(sharedReferenceQueue);
195+
public volatile ValueWrapperManager.HandleBlockWeakReference[] handleBlockSharedMap = new ValueWrapperManager.HandleBlockWeakReference[0];
196+
public final ValueWrapperManager.HandleBlockAllocator handleBlockAllocator = new ValueWrapperManager.HandleBlockAllocator();
197+
189198
@CompilationFinal public LanguageOptions options;
190199

191200
@CompilationFinal private AllocationReporter allocationReporter;

src/main/java/org/truffleruby/cext/CExtNodes.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ public abstract static class RbStrNewNulNode extends CoreMethodArrayArgumentsNod
597597
@Specialization
598598
protected RubyString rbStrNewNul(int byteLength,
599599
@Cached StringNodes.MakeStringNode makeStringNode) {
600-
final Rope rope = NativeRope.newBuffer(getContext().getFinalizationService(), byteLength, byteLength);
600+
final Rope rope = NativeRope.newBuffer(getContext(), byteLength, byteLength);
601601

602602
return makeStringNode.fromRope(rope);
603603
}
@@ -655,7 +655,7 @@ protected RubyString rbStrResize(RubyString string, int newByteLength,
655655
nativeRope.clearCodeRange();
656656
return string;
657657
} else {
658-
final NativeRope newRope = nativeRope.resize(getContext().getFinalizationService(), newByteLength);
658+
final NativeRope newRope = nativeRope.resize(getContext(), newByteLength);
659659

660660
// Like MRI's rb_str_resize()
661661
newRope.clearCodeRange();
@@ -1016,7 +1016,7 @@ protected NativeRope toNative(RubyString string,
10161016
nativeRope = (NativeRope) currentRope;
10171017
} else {
10181018
nativeRope = new NativeRope(
1019-
getContext().getFinalizationService(),
1019+
getContext(),
10201020
bytesNode.execute(currentRope),
10211021
currentRope.getEncoding(),
10221022
characterLengthNode.execute(currentRope),
@@ -1033,7 +1033,7 @@ protected NativeRope toNativeImmutable(ImmutableRubyString string) {
10331033
return ConcurrentOperations.getOrCompute(getContext().getImmutableNativeRopes(), string, s -> {
10341034
final LeafRope currentRope = s.rope;
10351035
return new NativeRope(
1036-
getContext().getFinalizationService(),
1036+
getContext(),
10371037
currentRope.getBytes(),
10381038
currentRope.getEncoding(),
10391039
currentRope.characterLength(),

src/main/java/org/truffleruby/cext/UnwrapNode.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
3333
import com.oracle.truffle.api.dsl.Cached;
3434
import com.oracle.truffle.api.dsl.CachedContext;
35+
import com.oracle.truffle.api.dsl.CachedLanguage;
3536
import com.oracle.truffle.api.dsl.Fallback;
3637
import com.oracle.truffle.api.dsl.GenerateUncached;
3738
import com.oracle.truffle.api.dsl.ImportStatic;
@@ -79,8 +80,9 @@ protected long unwrapTaggedLong(long handle) {
7980
@Specialization(guards = "isTaggedObject(handle)")
8081
protected Object unwrapTaggedObject(long handle,
8182
@CachedContext(RubyLanguage.class) RubyContext context,
83+
@CachedLanguage RubyLanguage language,
8284
@Cached BranchProfile noHandleProfile) {
83-
final ValueWrapper wrapper = context.getValueWrapperManager().getWrapperFromHandleMap(handle);
85+
final ValueWrapper wrapper = context.getValueWrapperManager().getWrapperFromHandleMap(handle, language);
8486
if (wrapper == null) {
8587
noHandleProfile.enter();
8688
raiseError(handle);
@@ -139,8 +141,9 @@ protected ValueWrapper unwrapTaggedLong(long handle) {
139141

140142
@Specialization(guards = "isTaggedObject(handle)")
141143
protected ValueWrapper unwrapTaggedObject(long handle,
142-
@CachedContext(RubyLanguage.class) RubyContext context) {
143-
return context.getValueWrapperManager().getWrapperFromHandleMap(handle);
144+
@CachedContext(RubyLanguage.class) RubyContext context,
145+
@CachedLanguage RubyLanguage language) {
146+
return context.getValueWrapperManager().getWrapperFromHandleMap(handle, language);
144147
}
145148

146149
@Fallback

src/main/java/org/truffleruby/cext/ValueWrapperManager.java

Lines changed: 117 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import org.truffleruby.cext.ValueWrapperManagerFactory.AllocateHandleNodeGen;
2121
import org.truffleruby.cext.ValueWrapperManagerFactory.GetHandleBlockHolderNodeGen;
2222
import org.truffleruby.extra.ffi.Pointer;
23+
import org.truffleruby.language.ImmutableRubyObject;
2324
import org.truffleruby.language.NotProvided;
2425
import org.truffleruby.language.RubyBaseNode;
2526

2627
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
2728
import com.oracle.truffle.api.dsl.Cached;
2829
import com.oracle.truffle.api.dsl.CachedContext;
30+
import com.oracle.truffle.api.dsl.CachedLanguage;
2931
import com.oracle.truffle.api.dsl.GenerateUncached;
3032
import com.oracle.truffle.api.dsl.Specialization;
3133
import com.oracle.truffle.api.interop.InteropLibrary;
@@ -37,7 +39,6 @@
3739
public class ValueWrapperManager {
3840

3941
static final long UNSET_HANDLE = -2L;
40-
static final HandleBlockAllocator allocator = new HandleBlockAllocator();
4142

4243
/* These constants are taken from ruby.h, and are based on us not tagging doubles. */
4344

@@ -74,6 +75,7 @@ public HandleThreadData makeThreadData() {
7475
HandleThreadData threadData = new HandleThreadData();
7576
HandleBlockHolder holder = threadData.holder;
7677
context.getFinalizationService().addFinalizer(
78+
context,
7779
threadData,
7880
ValueWrapperManager.class,
7981
() -> context.getMarkingService().queueForMarking(holder.handleBlock),
@@ -102,46 +104,88 @@ public ValueWrapper doubleWrapper(double value) {
102104
}
103105

104106
@TruffleBoundary
105-
public synchronized void addToBlockMap(HandleBlock block) {
107+
public synchronized void addToBlockMap(HandleBlock block, RubyLanguage language) {
106108
int blockIndex = block.getIndex();
107109
long blockBase = block.getBase();
108-
HandleBlockWeakReference[] map = blockMap;
109-
HandleBlockAllocator allocator = ValueWrapperManager.allocator;
110-
boolean grow = false;
111-
if (blockIndex + 1 > map.length) {
112-
final HandleBlockWeakReference[] copy = new HandleBlockWeakReference[blockIndex + 1];
113-
System.arraycopy(map, 0, copy, 0, map.length);
114-
map = copy;
115-
grow = true;
116-
}
110+
HandleBlockAllocator allocator = language.handleBlockAllocator;
111+
HandleBlockWeakReference[] map = growMapIfRequired(blockMap, blockIndex);
112+
blockMap = map;
117113
map[blockIndex] = new HandleBlockWeakReference(block);
118-
if (grow) {
119-
blockMap = map;
120-
}
121114

122-
context.getFinalizationService().addFinalizer(block, ValueWrapperManager.class, () -> {
115+
context.getFinalizationService().addFinalizer(context, block, ValueWrapperManager.class, () -> {
123116
this.blockMap[blockIndex] = null;
124117
allocator.addFreeBlock(blockBase);
125118
}, null);
126119
}
127120

128-
public ValueWrapper getWrapperFromHandleMap(long handle) {
121+
@TruffleBoundary
122+
public void addToSharedBlockMap(HandleBlock block, RubyLanguage language) {
123+
synchronized (language) {
124+
int blockIndex = block.getIndex();
125+
long blockBase = block.getBase();
126+
HandleBlockAllocator allocator = language.handleBlockAllocator;
127+
HandleBlockWeakReference[] map = growMapIfRequired(language.handleBlockSharedMap, blockIndex);
128+
language.handleBlockSharedMap = map;
129+
map[blockIndex] = new HandleBlockWeakReference(block);
130+
131+
language.sharedFinzationService.addFinalizer(context, block, ValueWrapperManager.class, () -> {
132+
language.handleBlockSharedMap[blockIndex] = null;
133+
allocator.addFreeBlock(blockBase);
134+
}, null);
135+
}
136+
}
137+
138+
private static HandleBlockWeakReference[] growMapIfRequired(HandleBlockWeakReference[] map, int blockIndex) {
139+
if (blockIndex + 1 > map.length) {
140+
final HandleBlockWeakReference[] copy = new HandleBlockWeakReference[blockIndex + 1];
141+
System.arraycopy(map, 0, copy, 0, map.length);
142+
map = copy;
143+
}
144+
return map;
145+
}
146+
147+
public ValueWrapper getWrapperFromHandleMap(long handle, RubyLanguage language) {
129148
final int index = HandleBlock.getHandleIndex(handle);
149+
final HandleBlock block = getBlockFromMap(index, language);
150+
if (block == null) {
151+
return null;
152+
}
153+
return block.getWrapper(handle);
154+
}
155+
156+
private HandleBlock getBlockFromMap(int index, RubyLanguage language) {
130157
final HandleBlockWeakReference[] blockMap = this.blockMap;
131-
final HandleBlockWeakReference ref;
158+
final HandleBlockWeakReference[] sharedMap = language.handleBlockSharedMap;
159+
HandleBlockWeakReference ref = null;
160+
// First try getting the block from the context's map
132161
if (index >= 0 && index < blockMap.length) {
133162
ref = blockMap[index];
134-
} else {
135-
return null;
163+
}
164+
// If no block was found in the context's map then look in the
165+
// shared map. If there is a block in a context's map then the
166+
// same block will not be in the shared map and vice versa.
167+
if (ref == null && index >= 0 && index < sharedMap.length) {
168+
ref = sharedMap[index];
136169
}
137170
if (ref == null) {
138171
return null;
139172
}
140-
final HandleBlock block = ref.get();
141-
if (block == null) {
142-
return null;
173+
return ref.get();
174+
}
175+
176+
public void freeAllBlocksInMap(RubyLanguage language) {
177+
HandleBlockWeakReference[] map = blockMap;
178+
HandleBlockAllocator allocator = language.handleBlockAllocator;
179+
180+
for (HandleBlockWeakReference ref : map) {
181+
if (ref == null) {
182+
continue;
183+
}
184+
HandleBlock block = ref.get();
185+
if (block != null) {
186+
allocator.addFreeBlock(block.base);
187+
}
143188
}
144-
return block.getWrapper(handle);
145189
}
146190

147191
protected static class FreeHandleBlock {
@@ -171,7 +215,7 @@ public long totalHandleAllocations() {
171215
private static final long OFFSET_MASK = ~BLOCK_MASK;
172216
public static final long ALLOCATION_BASE = 0x0badL << 48;
173217

174-
protected static class HandleBlockAllocator {
218+
public static class HandleBlockAllocator {
175219

176220
private long nextBlock = ALLOCATION_BASE;
177221
private FreeHandleBlock firstFreeBlock = null;
@@ -203,7 +247,7 @@ public static class HandleBlock {
203247
@SuppressWarnings("rawtypes") private final ValueWrapper[] wrappers;
204248
private int count;
205249

206-
public HandleBlock(RubyContext context) {
250+
public HandleBlock(RubyContext context, HandleBlockAllocator allocator) {
207251
this(context, allocator.getFreeBlock(), new ValueWrapper[BLOCK_SIZE]);
208252
}
209253

@@ -251,14 +295,15 @@ public static int getHandleIndex(long handle) {
251295
}
252296
}
253297

254-
protected static final class HandleBlockWeakReference extends WeakReference<HandleBlock> {
298+
public static final class HandleBlockWeakReference extends WeakReference<HandleBlock> {
255299
HandleBlockWeakReference(HandleBlock referent) {
256300
super(referent);
257301
}
258302
}
259303

260304
protected static class HandleBlockHolder {
261305
protected HandleBlock handleBlock = null;
306+
protected HandleBlock sharedHandleBlock = null;
262307
}
263308

264309
protected static class HandleThreadData {
@@ -269,8 +314,16 @@ public HandleBlock currentBlock() {
269314
return holder.handleBlock;
270315
}
271316

272-
public HandleBlock makeNewBlock(RubyContext context) {
273-
return (holder.handleBlock = new HandleBlock(context));
317+
public HandleBlock currentSharedBlock() {
318+
return holder.sharedHandleBlock;
319+
}
320+
321+
public HandleBlock makeNewBlock(RubyContext context, HandleBlockAllocator allocator) {
322+
return (holder.handleBlock = new HandleBlock(context, allocator));
323+
}
324+
325+
public HandleBlock makeNewSharedBlock(RubyContext context, HandleBlockAllocator allocator) {
326+
return (holder.sharedHandleBlock = new HandleBlock(context, allocator));
274327
}
275328
}
276329

@@ -311,12 +364,31 @@ public abstract static class AllocateHandleNode extends RubyBaseNode {
311364

312365
public abstract long execute(ValueWrapper wrapper);
313366

314-
@Specialization
367+
@Specialization(guards = "!isSharedObject(wrapper)")
315368
protected long allocateHandleOnKnownThread(ValueWrapper wrapper,
316369
@CachedContext(RubyLanguage.class) RubyContext context,
370+
@CachedLanguage RubyLanguage language,
317371
@Cached GetHandleBlockHolderNode getBlockHolderNode) {
318-
HandleThreadData threadData = getBlockHolderNode.execute(wrapper);
319-
HandleBlock block = threadData.holder.handleBlock;
372+
return allocateHandle(wrapper, context, language, getBlockHolderNode.execute(wrapper), false);
373+
}
374+
375+
@Specialization(guards = "isSharedObject(wrapper)")
376+
protected long allocateSharedHandleOnKnownThread(ValueWrapper wrapper,
377+
@CachedContext(RubyLanguage.class) RubyContext context,
378+
@CachedLanguage RubyLanguage language,
379+
@Cached GetHandleBlockHolderNode getBlockHolderNode) {
380+
return allocateHandle(wrapper, context, language, getBlockHolderNode.execute(wrapper), true);
381+
}
382+
383+
protected static long allocateHandle(ValueWrapper wrapper, RubyContext context, RubyLanguage language,
384+
HandleThreadData threadData, boolean shared) {
385+
HandleBlock block;
386+
if (shared) {
387+
block = threadData.holder.sharedHandleBlock;
388+
} else {
389+
block = threadData.holder.handleBlock;
390+
}
391+
320392
if (context.getOptions().CEXTS_TO_NATIVE_STATS) {
321393
context.getValueWrapperManager().recordHandleAllocation();
322394
}
@@ -325,12 +397,24 @@ protected long allocateHandleOnKnownThread(ValueWrapper wrapper,
325397
if (block != null) {
326398
context.getMarkingService().queueForMarking(block);
327399
}
328-
block = threadData.makeNewBlock(context);
329-
context.getValueWrapperManager().addToBlockMap(block);
400+
if (shared) {
401+
block = threadData
402+
.makeNewSharedBlock(context, language.handleBlockAllocator);
403+
context.getValueWrapperManager().addToSharedBlockMap(block, language);
404+
} else {
405+
block = threadData
406+
.makeNewBlock(context, language.handleBlockAllocator);
407+
context.getValueWrapperManager().addToBlockMap(block, language);
408+
}
409+
330410
}
331411
return block.setHandleOnWrapper(wrapper);
332412
}
333413

414+
protected static boolean isSharedObject(ValueWrapper wrapper) {
415+
return wrapper.getObject() instanceof ImmutableRubyObject;
416+
}
417+
334418
public static AllocateHandleNode create() {
335419
return AllocateHandleNodeGen.create();
336420
}

0 commit comments

Comments
 (0)