From 9810a50603920c3a32b23a1b476726245b340200 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Mon, 15 Dec 2014 12:19:51 +0000 Subject: [PATCH 01/12] [Truffle] Basic new implementation of large hashes. --- core/src/main/java/org/jruby/RubyHash.java | 2 +- .../jruby/truffle/nodes/core/ArrayNodes.java | 38 +- .../jruby/truffle/nodes/core/HashGuards.java | 21 +- .../jruby/truffle/nodes/core/HashNodes.java | 462 ++++++++---------- .../jruby/truffle/nodes/core/KernelNodes.java | 14 +- .../truffle/nodes/core/NilClassNodes.java | 2 +- .../jruby/truffle/nodes/core/SystemNode.java | 2 +- .../nodes/literal/HashLiteralNode.java | 24 +- .../methods/arguments/CheckArityNode.java | 2 +- .../arguments/ReadKeywordArgumentNode.java | 2 +- .../ReadKeywordRestArgumentNode.java | 14 +- .../truffle/runtime/core/CoreLibrary.java | 33 +- .../jruby/truffle/runtime/core/RubyHash.java | 300 +++++++++++- core/src/main/ruby/jruby/truffle/core.rb | 1 + .../main/ruby/jruby/truffle/core/config.rb | 17 + .../main/ruby/jruby/truffle/core/kernel.rb | 10 + spec/truffle/truffle.mspec | 1 + test/truffle/hash_stress_test.rb | 179 +++++++ 18 files changed, 759 insertions(+), 365 deletions(-) create mode 100644 core/src/main/ruby/jruby/truffle/core/config.rb create mode 100644 test/truffle/hash_stress_test.rb diff --git a/core/src/main/java/org/jruby/RubyHash.java b/core/src/main/java/org/jruby/RubyHash.java index b337f76a8b7..7f3e3f10540 100644 --- a/core/src/main/java/org/jruby/RubyHash.java +++ b/core/src/main/java/org/jruby/RubyHash.java @@ -314,7 +314,7 @@ private final void alloc(int buckets) { * ============================ */ - private static final int MRI_PRIMES[] = { + public static final int MRI_PRIMES[] = { 8 + 3, 16 + 3, 32 + 5, 64 + 3, 128 + 3, 256 + 27, 512 + 9, 1024 + 9, 2048 + 5, 4096 + 3, 8192 + 27, 16384 + 43, 32768 + 3, 65536 + 45, 131072 + 29, 262144 + 3, 524288 + 21, 1048576 + 7, 2097152 + 17, 4194304 + 15, 8388608 + 9, 16777216 + 43, 33554432 + 35, 67108864 + 15, diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java index 0079703d603..9c76edbda1f 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java @@ -1110,7 +1110,6 @@ public ClearNode(ClearNode prev) { @Specialization public RubyArray clear(RubyArray array) { notDesignedForCompilation(); - array.setSize(0); return array; } @@ -3551,6 +3550,43 @@ public RubyArray toA(RubyArray array) { } + @CoreMethod(names = "uniq") + public abstract static class UniqNode extends CoreMethodNode { + + public UniqNode(RubyContext context, SourceSection sourceSection) { + super(context, sourceSection); + } + + public UniqNode(UniqNode prev) { + super(prev); + } + + @Specialization + public RubyArray uniq(RubyArray array) { + notDesignedForCompilation(); + + final RubyArray uniq = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); + + for (Object value : array.slowToArray()) { + boolean duplicate = false; + + for (Object compare : uniq.slowToArray()) { + if ((boolean) DebugOperations.send(getContext(), value, "==", null, compare)) { + duplicate = true; + break; + } + } + + if (!duplicate) { + uniq.slowPush(value); + } + } + + return uniq; + } + + } + @CoreMethod(names = "unshift", argumentsAsArray = true) public abstract static class UnshiftNode extends CoreMethodNode { diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java index 3b7576ae815..698698be7a6 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java @@ -11,8 +11,6 @@ import org.jruby.truffle.runtime.core.RubyHash; -import java.util.LinkedHashMap; - public class HashGuards { public static boolean isNull(RubyHash hash) { @@ -20,23 +18,12 @@ public static boolean isNull(RubyHash hash) { } public static boolean isObjectArray(RubyHash hash) { - return hash.getStore() instanceof Object[]; - } - - public static boolean isObjectLinkedHashMap(RubyHash hash) { - return hash.getStore() instanceof LinkedHashMap; - } - - public static boolean isOtherNull(RubyHash hash, RubyHash other) { - return other.getStore() == null; - } - - public static boolean isOtherObjectArray(RubyHash hash, RubyHash other) { - return other.getStore() instanceof Object[]; + // Arrays are covariant in Java! + return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof RubyHash.Bucket[]); } - public static boolean isOtherObjectLinkedHashMap(RubyHash hash, RubyHash other) { - return other.getStore() instanceof LinkedHashMap; + public static boolean isBucketArray(RubyHash hash) { + return hash.getStore() instanceof RubyHash.Bucket[]; } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index fe8e3ac79b3..4b61f0df32d 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -9,8 +9,6 @@ */ package org.jruby.truffle.nodes.core; -import java.util.*; - import com.oracle.truffle.api.*; import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.source.*; @@ -18,6 +16,7 @@ import com.oracle.truffle.api.frame.*; import com.oracle.truffle.api.utilities.BranchProfile; import org.jruby.runtime.Visibility; +import org.jruby.truffle.nodes.RubyNode; import org.jruby.truffle.nodes.RubyRootNode; import org.jruby.truffle.nodes.dispatch.DispatchHeadNode; import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; @@ -27,6 +26,10 @@ import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + @CoreClass(name = "Hash") public abstract class HashNodes { @@ -45,88 +48,48 @@ public EqualNode(EqualNode prev) { equalNode = prev.equalNode; } - @Specialization(guards = {"isNull", "isOtherNull"}) + @Specialization(guards = {"isNull", "isNull(arguments[1])"}) public boolean equalNull(RubyHash a, RubyHash b) { return true; } - @Specialization(guards = {"isObjectArray", "isOtherObjectArray"}) - public boolean equalObjectArray(VirtualFrame frame, RubyHash a, RubyHash b) { + @Specialization + public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { notDesignedForCompilation(); - if (a == b) { - return true; - } - - final Object[] aStore = (Object[]) a.getStore(); - final int aSize = a.getStoreSize(); - - final Object[] bStore = (Object[]) b.getStore(); - final int bSize = b.getStoreSize(); + final List aEntries = a.verySlowToEntries(); + final List bEntries = a.verySlowToEntries(); - if (aSize != bSize) { + if (aEntries.size() != bEntries.size()) { return false; } - // TODO(CS): this is badly broken - I think it assumes the hash is ordered? - - for (int n = 0; n < aSize * 2; n++) { - if (!equalNode.call(frame, aStore[n], "==", null, bStore[n])) { - return false; - } - } - - return true; - } - - @Specialization(guards = {"isObjectLinkedHashMap", "isOtherObjectLinkedHashMap"}) - public boolean equalObjectLinkedHashMap(RubyHash a, RubyHash b) { - notDesignedForCompilation(); - throw new UnsupportedOperationException(); - } + // For each entry in a, check that there is a corresponding entry in b, and don't use entries in b more than once - @Specialization(guards = {"isObjectLinkedHashMap", "isOtherObjectArray"}) - public boolean equalObjectLinkedHashMapArray(VirtualFrame frame, RubyHash a, RubyHash b) { - notDesignedForCompilation(); - - final LinkedHashMap aStore = (LinkedHashMap) a.getStore(); - final int aSize = a.getStoreSize(); - - final Object[] bStore = (Object[]) b.getStore(); - final int bSize = b.getStoreSize(); - - if (aSize != bSize) { - return false; - } + final boolean[] bUsed = new boolean[bEntries.size()]; - // TODO(CS): this is crap - doesn't check for duplicates or anything - badly need to improve the Hash stuff + for (RubyHash.Entry aEntry : aEntries) { + boolean found = false; - for (Map.Entry entry : aStore.entrySet()) { - boolean match = false; + for (int n = 0; n < bEntries.size(); n++) { + if (!bUsed[n]) { + // TODO: cast - for (int n = 0; n < aSize * 2; n += 1) { - if (equalNode.call(frame, entry.getKey(), "==", null, bStore[n]) && equalNode.call(frame, entry.getValue(), "==", null, bStore[n + 1])) { - match = true; + if ((boolean) DebugOperations.send(getContext(), aEntry.getKey(), "eql?", null, bEntries.get(n).getKey())) { + bUsed[n] = true; + found = true; + break; + } } } - if (!match) { + if (!found) { return false; } } return true; } - - @Specialization(guards = "!isHash(arguments[1])") - public boolean equal(RubyHash a, Object b) { - notDesignedForCompilation(); - return false; - } - - protected boolean isHash(Object object) { - return object instanceof RubyHash; - } } @CoreMethod(names = "[]", onSingleton = true, argumentsAsArray = true) @@ -198,7 +161,7 @@ public RubyHash construct(Object[] args) { } } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, newStore, size); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, newStore, size, null); } else { largeObjectArray.enter(); throw new UnsupportedOperationException(); @@ -213,20 +176,15 @@ public RubyHash construct(Object[] args) { } } else { keyValues.enter(); - // Slow because we don't want the PE to see the hash map at all - return constructObjectLinkedMapMap(args); - } - } - @CompilerDirectives.SlowPath - public RubyHash constructObjectLinkedMapMap(Object[] args) { - final LinkedHashMap store = new LinkedHashMap<>(); + final List entries = new ArrayList<>(); - for (int n = 0; n < args.length; n += 2) { - store.put(args[n], args[n + 1]); - } + for (int n = 0; n < args.length; n += 2) { + entries.add(new RubyHash.Entry(args[n], args[n + 1])); + } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, store, 0); + return RubyHash.verySlowFromEntries(getContext(), entries); + } } } @@ -292,27 +250,17 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { } - @Specialization(guards = "isObjectLinkedHashMap") - public Object getObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isBucketArray") + public Object getBucketArray(RubyHash hash, Object key) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - // TODO(CS): not correct - using Java's Object#equals + final RubyHash.BucketAndIndex bucketAndIndex = hash.verySlowFindBucket(key); - final Object value = store.get(key); - - if (value == null) { - if (hash.getDefaultBlock() != null) { - return yield.dispatch(frame, hash.getDefaultBlock(), hash, key); - } else if (hash.getDefaultValue() != null) { - return hash.getDefaultValue(); - } else { - return getContext().getCoreLibrary().getNilObject(); - } + if (bucketAndIndex.getBucket() == null) { + return getContext().getCoreLibrary().getNilObject(); } - return value; + return bucketAndIndex.getBucket().value; } } @@ -324,7 +272,6 @@ public abstract static class SetIndexNode extends HashCoreMethodNode { private final BranchProfile considerExtendProfile = new BranchProfile(); private final BranchProfile extendProfile = new BranchProfile(); - private final BranchProfile transitionToLinkedHashMapProfile = new BranchProfile(); public SetIndexNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); @@ -342,7 +289,7 @@ public Object setNull(RubyHash hash, Object key, Object value) { final Object[] store = new Object[RubyHash.HASHES_SMALL * 2]; store[0] = key; store[1] = value; - hash.setStore(store, 1); + hash.setStore(store, 1, null, null); return value; } @@ -373,36 +320,56 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje return value; } + CompilerDirectives.transferToInterpreter(); + + // TODO(CS): need to watch for that transfer until we make the following fast path + + final List entries = hash.verySlowToEntries(); - transitionToLinkedHashMapProfile.enter(); + hash.setStore(new RubyHash.Bucket[RubyHash.capacityGreaterThan(newSize)], newSize, null, null); + + for (RubyHash.Entry entry : entries) { + hash.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + } + + hash.verySlowSetInBuckets(key, value); - transitionToLinkedHashMap(hash, store, key, value); return value; } - @CompilerDirectives.SlowPath - private void transitionToLinkedHashMap(RubyHash hash, Object[] oldStore, Object key, Object value) { - final LinkedHashMap newStore = new LinkedHashMap<>(); + @Specialization(guards = "isBucketArray") + public Object setBucketArray(RubyHash hash, Object key, Object value) { + notDesignedForCompilation(); - for (int n = 0; n < oldStore.length; n += 2) { - newStore.put(oldStore[n], oldStore[n + 1]); + if (hash.verySlowSetInBuckets(key, value)) { + hash.setStoreSize(hash.getStoreSize() + 1); } - newStore.put(key, value); - hash.setStore(newStore, 0); + return value; } - @Specialization(guards = "isObjectLinkedHashMap") - public Object setObjectLinkedHashMap(RubyHash hash, Object key, Object value) { - notDesignedForCompilation(); + } - hash.checkFrozen(this); + @CoreMethod(names = "clear") + public abstract static class ClearNode extends HashCoreMethodNode { - // TODO(CS): not correct - using Java's Object#equals + public ClearNode(RubyContext context, SourceSection sourceSection) { + super(context, sourceSection); + } - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - store.put(key, value); - return value; + public ClearNode(ClearNode prev) { + super(prev); + } + + @Specialization(guards = "isNull") + public RubyHash emptyNull(RubyHash hash) { + return hash; + } + + @Specialization(guards = "!isNull") + public RubyHash empty(RubyHash hash) { + hash.setStore(null, 0, null, null); + return hash; } } @@ -410,12 +377,16 @@ public Object setObjectLinkedHashMap(RubyHash hash, Object key, Object value) { @CoreMethod(names = "delete", required = 1) public abstract static class DeleteNode extends HashCoreMethodNode { + @Child protected PredicateDispatchHeadNode eqlNode; + public DeleteNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); + eqlNode = new PredicateDispatchHeadNode(context); } public DeleteNode(DeleteNode prev) { super(prev); + eqlNode = prev.eqlNode; } @Specialization(guards = "isNull") @@ -425,50 +396,69 @@ public RubyNilClass deleteNull(RubyHash hash, Object key) { } @Specialization(guards = "isObjectArray") - public Object deleteObjectArray(RubyHash hash, Object key) { - notDesignedForCompilation(); + public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { + hash.checkFrozen(this); - // TODO(CS): seriously not correct + final Object[] store = (Object[]) hash.getStore(); + final int size = hash.getStoreSize(); - hash.checkFrozen(this); + for (int n = 0; n < RubyHash.HASHES_SMALL * 2; n += 2) { + if (n < size && eqlNode.call(frame, store[n], "eql?", null, key)) { + final Object value = store[n + 1]; - final Object[] oldStore = (Object[]) hash.getStore(); + // Move the later values down + System.arraycopy(store, n + 2, store, n, RubyHash.HASHES_SMALL * 2 - n - 2); - final LinkedHashMap newStore = new LinkedHashMap<>(); - hash.setStore(newStore, 0); + hash.setStoreSize(size - 1); - for (int n = 0; n < hash.getStoreSize(); n++) { - newStore.put(oldStore[n * 2], oldStore[n * 2 + 1]); + return value; + } } - // TODO(CS): seriously not correct - using Java's Object#equals + return getContext().getCoreLibrary().getNilObject(); + } + + @Specialization(guards = "isBucketArray") + public Object delete(RubyHash hash, Object key) { + notDesignedForCompilation(); - final Object removed = newStore.remove(key); + final RubyHash.BucketAndIndex bucketAndIndex = hash.verySlowFindBucket(key); - if (removed == null) { + if (bucketAndIndex.getBucket() == null) { return getContext().getCoreLibrary().getNilObject(); - } else { - return removed; } - } - @Specialization(guards = "isObjectLinkedHashMap") - public Object delete(RubyHash hash, Object key) { - notDesignedForCompilation(); + final RubyHash.Bucket bucket = bucketAndIndex.getBucket(); - hash.checkFrozen(this); + // Remove from the sequence chain - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); + if (bucket.previousInSequence == null) { + hash.firstInSequence = bucket.nextInSequence; + } else { + bucket.previousInSequence.nextInSequence = bucket.nextInSequence; + } - // TODO(CS): seriously not correct - using Java's Object#equals + if (bucket.nextInSequence == null) { + hash.lastInSequence = bucket.previousInSequence; + } else { + bucket.nextInSequence.previousInSequence = bucket.previousInSequence; + } - final Object removed = store.remove(key); + // Remove from the lookup chain - if (removed == null) { - return getContext().getCoreLibrary().getNilObject(); + if (bucket.previousInLookup == null) { + ((RubyHash.Bucket[]) hash.getStore())[bucketAndIndex.getIndex()] = bucket.nextInLookup; } else { - return removed; + bucket.previousInLookup.nextInLookup = bucket.nextInLookup; + } + + if (bucket.nextInLookup != null) { + bucket.nextInLookup.previousInLookup = bucket.previousInLookup; } + + hash.setStoreSize(hash.getStoreSize() - 1); + + return bucket.value; } } @@ -521,26 +511,12 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc return hash; } - @Specialization(guards = "isObjectLinkedHashMap") - public RubyHash eachObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isBucketArray") + public RubyHash eachBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - int count = 0; - - try { - for (Map.Entry entry : store.entrySet()) { - if (CompilerDirectives.inInterpreter()) { - count++; - } - - yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue())); - } - } finally { - if (CompilerDirectives.inInterpreter()) { - ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count); - } + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue())); } return hash; @@ -564,19 +540,11 @@ public boolean emptyNull(RubyHash hash) { return true; } - @Specialization(guards = "isObjectArray") + @Specialization(guards = "!isNull") public boolean emptyObjectArray(RubyHash hash) { return hash.getStoreSize() == 0; } - @Specialization(guards = "isObjectLinkedHashMap") - public boolean emptyObjectLinkedHashMap(RubyHash hash) { - notDesignedForCompilation(); - - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - return store.isEmpty(); - } - } @CoreMethod(names = "initialize", needsBlock = true, optional = 1) @@ -593,7 +561,7 @@ public InitializeNode(InitializeNode prev) { @Specialization public RubyNilClass initialize(RubyHash hash, UndefinedPlaceholder defaultValue, UndefinedPlaceholder block) { notDesignedForCompilation(); - hash.setStore(null, 0); + hash.setStore(null, 0, null, null); hash.setDefaultBlock(null); return getContext().getCoreLibrary().getNilObject(); } @@ -601,7 +569,7 @@ public RubyNilClass initialize(RubyHash hash, UndefinedPlaceholder defaultValue, @Specialization public RubyNilClass initialize(RubyHash hash, UndefinedPlaceholder defaultValue, RubyProc block) { notDesignedForCompilation(); - hash.setStore(null, 0); + hash.setStore(null, 0, null, null); hash.setDefaultBlock(block); return getContext().getCoreLibrary().getNilObject(); } @@ -626,7 +594,7 @@ public InitializeCopyNode(InitializeCopyNode prev) { super(prev); } - @Specialization(guards = "isOtherNull") + @Specialization(guards = "isNull(arguments[1])") public RubyHash dupNull(RubyHash self, RubyHash from) { notDesignedForCompilation(); @@ -636,12 +604,12 @@ public RubyHash dupNull(RubyHash self, RubyHash from) { self.setDefaultBlock(from.getDefaultBlock()); self.setDefaultValue(from.getDefaultValue()); - self.setStore(null, 0); + self.setStore(null, 0, null, null); return self; } - @Specialization(guards = "isOtherObjectArray") + @Specialization(guards = "isObjectArray(arguments[1])") public RubyHash dupObjectArray(RubyHash self, RubyHash from) { notDesignedForCompilation(); @@ -650,25 +618,22 @@ public RubyHash dupObjectArray(RubyHash self, RubyHash from) { } final Object[] store = (Object[]) from.getStore(); - self.setStore(Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2), store.length); + self.setStore(Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2), store.length, null, null); self.setDefaultBlock(from.getDefaultBlock()); self.setDefaultValue(from.getDefaultValue()); return self; } - @Specialization(guards = "isOtherObjectLinkedHashMap") - public RubyHash dupObjectLinkedHashMap(RubyHash self, RubyHash from) { + @Specialization(guards = "isBucketArray(arguments[1])") + public RubyHash dupBucketArray(RubyHash self, RubyHash from) { notDesignedForCompilation(); if (self == from) { return self; } - final LinkedHashMap store = (LinkedHashMap) from.getStore(); - self.setStore(new LinkedHashMap<>(store), store.size()); - self.setDefaultBlock(from.getDefaultBlock()); - self.setDefaultValue(from.getDefaultValue()); + self.verySlowSetEntries(from.verySlowToEntries()); return self; } @@ -697,52 +662,21 @@ public RubyString inspectNull(RubyHash hash) { return getContext().makeString("{}"); } - @Specialization(guards = "isObjectArray") + @Specialization public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) { notDesignedForCompilation(); - final Object[] store = (Object[]) hash.getStore(); - final StringBuilder builder = new StringBuilder(); builder.append("{"); - for (int n = 0; n < hash.getStoreSize(); n++) { - if (n > 0) { + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + if (builder.length() > 1) { builder.append(", "); } // TODO(CS): to string - builder.append(inspect.call(frame, store[n * 2], "inspect", null)); - builder.append("=>"); - builder.append(inspect.call(frame, store[n * 2 + 1], "inspect", null)); - } - - builder.append("}"); - - return getContext().makeString(builder.toString()); - } - - @Specialization(guards = "isObjectLinkedHashMap") - public RubyString inspectObjectLinkedHashMap(VirtualFrame frame, RubyHash hash) { - notDesignedForCompilation(); - - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - final StringBuilder builder = new StringBuilder(); - - builder.append("{"); - - boolean first = true; - - for (Map.Entry entry : store.entrySet()) { - if (first) { - first = false; - } else { - builder.append(", "); - } - builder.append(inspect.call(frame, entry.getKey(), "inspect", null)); builder.append("=>"); builder.append(inspect.call(frame, entry.getValue(), "inspect", null)); @@ -791,15 +725,17 @@ public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { return false; } - @Specialization(guards = "isObjectLinkedHashMap") - public boolean keyObjectLinkedHashMap(RubyHash hash, Object key) { + @Specialization(guards = "isBucketArray") + public boolean keyBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - // TODO(CS): seriously not correct - using Java's Object#equals + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + if (eqlNode.call(frame, entry.getKey(), "eql?", null, key)) { + return true; + } + } - return store.containsKey(key); + return false; } } @@ -835,19 +771,19 @@ public RubyArray keysObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length); } - @Specialization(guards = "isObjectLinkedHashMap") - public RubyArray keysObjectLinkedHashMap(RubyHash hash) { + @Specialization(guards = "isBucketArray") + public RubyArray keysBucketArray(RubyHash hash) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - final Object[] keys = new Object[store.size()]; + final Object[] keys = new Object[hash.getStoreSize()]; + RubyHash.Bucket bucket = hash.firstInSequence; int n = 0; - for (Object key : store.keySet()) { - keys[n] = key; + while (bucket != null) { + keys[n] = bucket.key; n++; + bucket = bucket.nextInSequence; } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length); @@ -899,31 +835,17 @@ public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc return new RubyArray(getContext().getCoreLibrary().getArrayClass(), result, resultSize); } - @Specialization(guards = "isObjectLinkedHashMap") - public RubyArray mapObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isBucketArray") + public RubyArray mapBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - final RubyArray result = new RubyArray(getContext().getCoreLibrary().getArrayClass()); + final RubyArray array = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); - int count = 0; - - try { - for (Map.Entry entry : store.entrySet()) { - if (CompilerDirectives.inInterpreter()) { - count++; - } - - result.slowPush(yield(frame, block, entry.getKey(), entry.getValue())); - } - } finally { - if (CompilerDirectives.inInterpreter()) { - ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count); - } + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + array.slowPush(yield(frame, block, entry.getKey(), entry.getValue())); } - return result; + return array; } } @@ -951,16 +873,16 @@ public MergeNode(MergeNode prev) { eqlNode = prev.eqlNode; } - @Specialization(guards = {"isObjectArray", "isOtherNull"}) + @Specialization(guards = {"isObjectArray", "isNull(arguments[1])"}) public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) { final Object[] store = (Object[]) hash.getStore(); final Object[] copy = Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getStoreSize()); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getStoreSize(), null); } @ExplodeLoop - @Specialization(guards = {"isObjectArray", "isOtherObjectArray"}) + @Specialization(guards = {"isObjectArray", "isObjectArray(arguments[1])"}) public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, RubyHash other) { // TODO(CS): what happens with the default block here? Which side does it get merged from? @@ -996,14 +918,14 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R if (mergeFromACount == 0) { nothingFromFirstProfile.enter(); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize, null); } considerNothingFromSecondProfile.enter(); if (mergeFromACount == storeB.length) { nothingFromSecondProfile.enter(); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize, null); } considerResultIsSmallProfile.enter(); @@ -1031,13 +953,29 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R index += 2; } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), merged, mergedSize); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), merged, mergedSize, null); } CompilerDirectives.transferToInterpreter(); throw new UnsupportedOperationException(); } + + @Specialization + public RubyHash mergeBucketArrayBucketArray(VirtualFrame frame, RubyHash hash, RubyHash other) { + final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new RubyHash.Bucket[RubyHash.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); + + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + } + + for (RubyHash.Entry entry : other.verySlowToEntries()) { + merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + } + + return merged; + } + } @CoreMethod(names = "default", optional = 1) @@ -1092,17 +1030,11 @@ public int sizeNull(RubyHash hash) { return 0; } - @Specialization(guards = "isObjectArray") + @Specialization(guards = "!isNull") public int sizeObjectArray(RubyHash hash) { return hash.getStoreSize(); } - @Specialization(guards = "isObjectLinkedHashMap") - public int sizeObjectLinkedHashMap(RubyHash hash) { - notDesignedForCompilation(); - return ((LinkedHashMap) hash.getStore()).size(); - } - } @CoreMethod(names = "values") @@ -1134,19 +1066,19 @@ public RubyArray valuesObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length); } - @Specialization(guards = "isObjectLinkedHashMap") - public RubyArray valuesObjectLinkedHashMap(RubyHash hash) { + @Specialization(guards = "isBucketArray") + public RubyArray valuesBucketArray(RubyHash hash) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); - - final Object[] values = new Object[store.size()]; + final Object[] values = new Object[hash.getStoreSize()]; + RubyHash.Bucket bucket = hash.firstInSequence; int n = 0; - for (Object value : store.values()) { - values[n] = value; + while (bucket != null) { + values[n] = bucket.value; n++; + bucket = bucket.nextInSequence; } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length); @@ -1187,18 +1119,18 @@ public RubyArray toArrayObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), pairs, size); } - @Specialization(guards = "isObjectLinkedHashMap") - public RubyArray toArrayLinkedHashMap(RubyHash hash) { + @Specialization(guards = "isBucketArray") + public RubyArray toArrayBucketArray(RubyHash hash) { notDesignedForCompilation(); - final LinkedHashMap store = (LinkedHashMap) hash.getStore(); final int size = hash.getStoreSize(); final Object[] pairs = new Object[size]; + int n = 0; - for (Map.Entry pair : store.entrySet()) { - pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), pair.getKey(), pair.getValue()); - n += 1; + for (RubyHash.Entry entry : hash.verySlowToEntries()) { + pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getValue(), entry.getValue()); + n++; } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), pairs, size); diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java index 9c3702c4700..a39babb51d4 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java @@ -596,8 +596,7 @@ private static void exec(RubyContext context, String[] commandLine) { final RubyHash env = context.getCoreLibrary().getENV(); - // TODO(CS): cast - for (Map.Entry entry : ((LinkedHashMap) env.getStore()).entrySet()) { + for (RubyHash.Entry entry : env.verySlowToEntries()) { builder.environment().put(entry.getKey().toString(), entry.getValue().toString()); } @@ -839,23 +838,24 @@ public HashNode(HashNode prev) { @Specialization public int hash(int value) { + // TODO(CS): should check this matches MRI return value; } @Specialization public int hash(long value) { - return (int) (value ^ value >>> 32); + // TODO(CS): should check this matches MRI + return Long.valueOf(value).hashCode(); } @Specialization - public int hash(RubyBignum value) { - return value.hashCode(); + public int hash(double value) { + // TODO(CS): should check this matches MRI + return Double.valueOf(value).hashCode(); } @Specialization public int hash(RubyBasicObject self) { - notDesignedForCompilation(); - return self.hashCode(); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/NilClassNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/NilClassNodes.java index f427ed4a065..85e30b84845 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/NilClassNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/NilClassNodes.java @@ -133,7 +133,7 @@ public ToHNode(ToHNode prev) { @Specialization public RubyHash toH() { - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, getContext().getCoreLibrary().getNilObject(), null, 0); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, getContext().getCoreLibrary().getNilObject(), null, 0, null); } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java index 6c0471c9fb6..ddb5e3e1089 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java @@ -45,7 +45,7 @@ public Object execute(VirtualFrame frame) { final List envp = new ArrayList<>(); // TODO(CS): cast - for (Map.Entry entry : ((LinkedHashMap) env.getStore()).entrySet()) { + for (RubyHash.Entry entry : env.verySlowToEntries()) { envp.add(entry.getKey().toString() + "=" + entry.getValue().toString()); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java index 3aed0b13267..bc5460f10fd 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java @@ -19,7 +19,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyString; -import java.util.LinkedHashMap; +import java.util.*; public abstract class HashLiteralNode extends RubyNode { @@ -69,7 +69,7 @@ public EmptyHashLiteralNode(RubyContext context, SourceSection sourceSection) { @ExplodeLoop @Override public RubyHash executeRubyHash(VirtualFrame frame) { - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0, null); } } @@ -111,40 +111,30 @@ public RubyHash executeRubyHash(VirtualFrame frame) { position += 2; } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, position / 2); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, position / 2, null); } } public static class GenericHashLiteralNode extends HashLiteralNode { - @Child protected DispatchHeadNode equalNode; - public GenericHashLiteralNode(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) { super(context, sourceSection, keyValues); - equalNode = new DispatchHeadNode(context); } - @ExplodeLoop @Override public RubyHash executeRubyHash(VirtualFrame frame) { notDesignedForCompilation(); - final LinkedHashMap storage = new LinkedHashMap<>(); + final List entries = new ArrayList<>(); for (int n = 0; n < keyValues.length; n += 2) { - Object key = keyValues[n].execute(frame); - - if (key instanceof RubyString) { - key = freezeNode.call(frame, dupNode.call(frame, key, "dup", null), "freeze", null); - } - + final Object key = keyValues[n].execute(frame); final Object value = keyValues[n + 1].execute(frame); - - storage.put(key, value); + entries.add(new RubyHash.Entry(key, value)); } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, 0); + return RubyHash.verySlowFromEntries(getContext(), entries); } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java index a745e33c8df..0a159600342 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java @@ -50,7 +50,7 @@ public void executeVoid(VirtualFrame frame) { } if (!keywordsRest && arity.hasKeywords() && getKeywordsHash(frame) != null) { - for (Map.Entry entry : getKeywordsHash(frame).slowToMap().entrySet()) { + for (RubyHash.Entry entry : getKeywordsHash(frame).verySlowToEntries()) { for (String keyword : keywords) { if (!keyword.toString().equals(entry.getKey().toString())) { throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + entry.getKey().toString(), this)); diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java index 319dc87f456..36fdac97308 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java @@ -47,7 +47,7 @@ public Object execute(VirtualFrame frame) { Object value = null; - for (Map.Entry entry : hash.slowToMap().entrySet()) { + for (RubyHash.Entry entry : hash.verySlowToEntries()) { if (entry.getKey().toString().equals(name)) { value = entry.getValue(); break; diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java index a95b27f6272..fdc0c641df7 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java @@ -16,9 +16,7 @@ import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.*; public class ReadKeywordRestArgumentNode extends RubyNode { @@ -38,22 +36,22 @@ public Object execute(VirtualFrame frame) { final RubyHash hash = getKeywordsHash(frame); if (hash == null) { - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0, null); } - final LinkedHashMap store = new LinkedHashMap<>(); + final List entries = new ArrayList<>(); - outer: for (Map.Entry entry : hash.slowToMap().entrySet()) { + outer: for (RubyHash.Entry entry : hash.verySlowToEntries()) { for (String excludedKeyword : excludedKeywords) { if (excludedKeyword.toString().equals(entry.getKey().toString())) { continue outer; } } - store.put(entry.getKey(), entry.getValue()); + entries.add(new RubyHash.Entry(entry.getKey(), entry.getValue())); } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, store, store.size()); + return RubyHash.verySlowFromEntries(getContext(), entries); } private RubyHash getKeywordsHash(VirtualFrame frame) { diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java index 37a80d40044..babf1c4083e 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java @@ -15,13 +15,10 @@ import com.oracle.truffle.api.source.Source; import org.jcodings.Encoding; import org.jcodings.EncodingDB; -import org.jruby.embed.variable.Constant; import org.jruby.runtime.Constants; -import org.jruby.runtime.Visibility; import org.jruby.runtime.encoding.EncodingService; import org.jruby.truffle.nodes.RubyNode; import org.jruby.truffle.nodes.core.ArrayNodes; -import org.jruby.truffle.runtime.ModuleOperations; import org.jruby.truffle.runtime.RubyCallStack; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.rubinius.RubiniusLibrary; @@ -32,7 +29,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; -import java.util.LinkedHashMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; public class CoreLibrary { @@ -245,13 +244,6 @@ public void initialize() { objectClass.setConstant(null, "RUBY_ENGINE", RubyString.fromJavaString(stringClass, Constants.ENGINE + "+truffle")); objectClass.setConstant(null, "RUBY_PLATFORM", RubyString.fromJavaString(stringClass, Constants.PLATFORM)); - final LinkedHashMap configHashMap = new LinkedHashMap<>(); - configHashMap.put(RubyString.fromJavaString(stringClass, "ruby_install_name"), RubyString.fromJavaString(stringClass, "rubytruffle")); - configHashMap.put(RubyString.fromJavaString(stringClass, "RUBY_INSTALL_NAME"), RubyString.fromJavaString(stringClass, "rubytruffle")); - configHashMap.put(RubyString.fromJavaString(stringClass, "host_os"), RubyString.fromJavaString(stringClass, "unknown")); - configHashMap.put(RubyString.fromJavaString(stringClass, "exeext"), RubyString.fromJavaString(stringClass, "")); - configHashMap.put(RubyString.fromJavaString(stringClass, "EXEEXT"), RubyString.fromJavaString(stringClass, "rubytruffle")); - edomClass = new RubyException.RubyExceptionClass(context, errnoModule, systemCallErrorClass, "EDOM"); new RubyClass(context, errnoModule, systemCallErrorClass, "ENOENT"); new RubyClass(context, errnoModule, systemCallErrorClass, "EPERM"); @@ -263,9 +255,6 @@ public void initialize() { // TODO(cs): this should be a separate exception mathModule.setConstant(null, "DomainError", edomClass); - // TODO(cs): the alias should be the other way round, Config is legacy (and should warn). - objectClass.setConstant(null, "RbConfig", configModule); - // Create some key objects mainObject = new RubyBasicObject(objectClass); @@ -287,12 +276,7 @@ public void initialize() { arrayMaxBlock = new ArrayNodes.MaxBlock(context); argv = new RubyArray(arrayClass); - envHash = getSystemEnv(); objectClass.setConstant(null, "ARGV", argv); - objectClass.setConstant(null, "ENV", envHash); - - final RubyHash configHash = new RubyHash(hashClass, null, null, configHashMap, 0); - configModule.setConstant(null, "CONFIG", configHash); fileClass.setConstant(null, "SEPARATOR", RubyString.fromJavaString(stringClass, File.separator)); fileClass.setConstant(null, "Separator", RubyString.fromJavaString(stringClass, File.separator)); @@ -313,6 +297,11 @@ public void initializeAfterMethodsAdded() { if (Options.TRUFFLE_LOAD_CORE.load()) { loadRubyCore("jruby/truffle/core.rb"); } + + // ENV is supposed to be an object that actually updates the environment, and sees any updates + + envHash = getSystemEnv(); + objectClass.setConstant(null, "ENV", envHash); } public void loadRubyCore(String fileName) { @@ -747,13 +736,13 @@ public RubyHash getENV() { public RubyEncoding getDefaultEncoding() { return RubyEncoding.getEncoding(context, "US-ASCII"); } private RubyHash getSystemEnv() { - final LinkedHashMap storage = new LinkedHashMap<>(); + final List entries = new ArrayList<>(); for (Map.Entry variable : System.getenv().entrySet()) { - storage.put(context.makeString(variable.getKey()), context.makeString(variable.getValue())); + entries.add(new RubyHash.Entry(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); } - return new RubyHash(context.getCoreLibrary().getHashClass(), null, null, storage, 0); + return RubyHash.verySlowFromEntries(context, entries); } public ArrayNodes.MinBlock getArrayMinBlock() { diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java index 0905bb53c5c..b8b485fb149 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java @@ -11,7 +11,9 @@ import java.util.*; +import com.oracle.truffle.api.CompilerDirectives; import org.jruby.truffle.nodes.RubyNode; +import org.jruby.truffle.runtime.DebugOperations; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.subsystems.ObjectSpaceManager; import org.jruby.util.cli.Options; @@ -21,8 +23,14 @@ */ public class RubyHash extends RubyBasicObject { + // TODO(CS): I think we need to salt the hash somehow - there have been DOS attacks on Ruby for that in the past + public static final int HASHES_SMALL = Options.TRUFFLE_HASHES_SMALL.load(); + public static final int[] CAPACITIES = Arrays.copyOf(org.jruby.RubyHash.MRI_PRIMES, org.jruby.RubyHash.MRI_PRIMES.length - 1); + + private static final int SIGN_BIT_MASK = ~(1 << 31); + /** * The class from which we create the object that is {@code Hash}. A subclass of * {@link org.jruby.truffle.runtime.core.RubyClass} so that we can override {@link RubyClass#newInstance} and allocate a @@ -36,7 +44,7 @@ public RubyHashClass(RubyContext context, RubyClass objectClass) { @Override public RubyBasicObject newInstance(RubyNode currentNode) { - return new RubyHash(this, null, null, null, 0); + return new RubyHash(this, null, null, null, 0, null); } } @@ -45,18 +53,23 @@ public RubyBasicObject newInstance(RubyNode currentNode) { private Object defaultValue; private Object store; private int storeSize; + public Bucket firstInSequence; + public Bucket lastInSequence; - public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize) { + public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize, Bucket firstInSequence) { super(rubyClass); - assert store == null || store instanceof Object[] || store instanceof LinkedHashMap; - assert !(store instanceof Object[]) || ((Object[]) store).length == HASHES_SMALL * 2; - assert !(store instanceof Object[]) || storeSize <= HASHES_SMALL; + final boolean isObjectArray = store instanceof Object[] && !(store instanceof RubyHash.Bucket[]); + assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); + assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); + assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); + assert !isObjectArray || (firstInSequence == null) : store.getClass(); this.defaultBlock = defaultBlock; this.defaultValue = defaultValue; this.store = store; this.storeSize = storeSize; + this.firstInSequence = firstInSequence; } public RubyProc getDefaultBlock() { @@ -83,42 +96,47 @@ public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } - public void setStore(Object store, int storeSize) { - assert store == null || store instanceof Object[] || store instanceof LinkedHashMap; - assert !(store instanceof Object[]) || ((Object[]) store).length == HASHES_SMALL * 2; - assert !(store instanceof Object[]) || storeSize <= HASHES_SMALL; - + public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket lastInSequence) { + final boolean isObjectArray = store instanceof Object[] && !(store instanceof RubyHash.Bucket[]); + assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); + assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); + assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); + assert !isObjectArray || (firstInSequence == null) : store.getClass(); this.store = store; this.storeSize = storeSize; + this.firstInSequence = firstInSequence; + this.lastInSequence = lastInSequence; } public void setStoreSize(int storeSize) { - assert storeSize <= HASHES_SMALL; this.storeSize = storeSize; } - public Map slowToMap() { - if (store == null) { - return Collections.EMPTY_MAP; - } if (store instanceof Object[]) { - final Map map = new HashMap<>(); + public List verySlowToEntries() { + final List entries = new ArrayList<>(); + if (store instanceof Bucket[]) { + Bucket bucket = firstInSequence; + + while (bucket != null) { + entries.add(new Entry(bucket.key, bucket.value)); + bucket = bucket.nextInSequence; + } + } else if (store instanceof Object[]) { for (int n = 0; n < storeSize; n++) { - map.put(((Object[]) store)[n * 2], ((Object[]) store)[n * 2 + 1]); + entries.add(new Entry(((Object[]) store)[n * 2], ((Object[]) store)[n * 2 + 1])); } - - return map; - } else if (store instanceof LinkedHashMap) { - return (LinkedHashMap) store; - } else { + } else if (store != null) { throw new UnsupportedOperationException(); } + + return entries; } @Override public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visitor) { - for (Map.Entry entry : slowToMap().entrySet()) { + for (RubyHash.Entry entry : verySlowToEntries()) { if (entry.getKey() instanceof RubyBasicObject) { ((RubyBasicObject) entry.getKey()).visitObjectGraph(visitor); } @@ -129,4 +147,240 @@ public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visit } } + public static class BucketAndIndex { + + private final Bucket bucket; + private final int index; + private final Bucket endOfLookupChain; + + public BucketAndIndex(Bucket bucket, int index, Bucket endOfLookupChain) { + this.bucket = bucket; + this.index = index; + this.endOfLookupChain = endOfLookupChain; + } + + public Bucket getBucket() { + return bucket; + } + + public int getIndex() { + return index; + } + + public Bucket getEndOfLookupChain() { + return endOfLookupChain; + } + } + + public static class Entry { + + private final Object key; + private final Object value; + + public Entry(Object key, Object value) { + assert key != null; + assert value != null; + + this.key = key; + this.value = value; + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + + } + + public static class Bucket { + + public Object key; + public Object value; + + public Bucket previousInLookup; + public Bucket nextInLookup; + + public Bucket previousInSequence; + public Bucket nextInSequence; + + } + + public static int capacityGreaterThan(int size) { + for (int capacity : CAPACITIES) { + if (capacity > size) { + return capacity; + } + } + + return CAPACITIES[CAPACITIES.length - 1]; + } + + @CompilerDirectives.SlowPath + public BucketAndIndex verySlowFindBucket(Object key) { + final Bucket[] buckets = (Bucket[]) store; + + // Hash + + // TODO: cast + + final Object hashValue = DebugOperations.send(getContext(), key, "hash", null); + + final int hashed; + + if (hashValue instanceof Integer) { + hashed = (int) hashValue; + } else if (hashValue instanceof Long) { + hashed = (int) (long) hashValue; + } else { + throw new UnsupportedOperationException(); + } + + final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; + + // Find the initial bucket + + Bucket bucket = buckets[bucketIndex]; + + // Go through the chain of buckets to see if we're going to overwrite a key or append a new bucket + + Bucket endOfLookupChain = null; + + while (bucket != null) { + // TODO: cast + + if ((boolean) DebugOperations.send(getContext(), key, "eql?", null, bucket.key)) { + return new BucketAndIndex(bucket, bucketIndex, bucket); + } + + endOfLookupChain = bucket; + bucket = bucket.nextInLookup; + } + + return new BucketAndIndex(null, bucketIndex, endOfLookupChain); + } + + @CompilerDirectives.SlowPath + public void verySlowSetAtBucket(BucketAndIndex bucketAndIndex, Object key, Object value) { + if (bucketAndIndex.getBucket() == null) { + final Bucket bucket = new RubyHash.Bucket(); + bucket.key = key; + bucket.value = value; + + if (firstInSequence == null) { + firstInSequence = bucket; + lastInSequence = bucket; + } else { + lastInSequence.nextInSequence = bucket; + bucket.previousInSequence = lastInSequence; + lastInSequence = bucket; + } + + if (bucketAndIndex.getEndOfLookupChain() == null) { + ((Bucket[]) store)[bucketAndIndex.getIndex()] = bucket; + } else { + bucketAndIndex.getEndOfLookupChain().nextInLookup = bucket; + bucket.previousInLookup = bucketAndIndex.getEndOfLookupChain(); + } + } else { + final Bucket bucket = bucketAndIndex.getBucket(); + + // The bucket stays in the same place in the sequence + + // Update the key (it overwrites even it it's eql?) and value + + bucket.key = key; + bucket.value = value; + } + } + + @CompilerDirectives.SlowPath + public boolean verySlowSetInBuckets(Object key, Object value) { + if (key instanceof RubyString) { + key = DebugOperations.send(getContext(), DebugOperations.send(getContext(), key, "dup", null), "freeze", null); + } + + final BucketAndIndex bucketAndIndex = verySlowFindBucket(key); + verySlowSetAtBucket(bucketAndIndex, key, value); + return bucketAndIndex.getBucket() == null; + } + + @CompilerDirectives.SlowPath + public static RubyHash verySlowFromEntries(RubyContext context, List entries) { + RubyNode.notDesignedForCompilation(); + + final RubyHash hash = new RubyHash(context.getCoreLibrary().getHashClass(), null, null, null, 0, null); + hash.verySlowSetEntries(entries); + return hash; + } + + @CompilerDirectives.SlowPath + public void verySlowSetEntries(List entries) { + final int size = entries.size(); + setStore(new Bucket[RubyHash.capacityGreaterThan(size)], 0, null, null); + + int actualSize = 0; + + for (Entry entry : entries) { + if (verySlowSetInBuckets(entry.getKey(), entry.getValue())) { + actualSize++; + } + } + + setStoreSize(actualSize); + } + + public void dump() { + final StringBuilder builder = new StringBuilder(); + + builder.append("("); + + for (Bucket bucket : (Bucket[]) store) { + builder.append("("); + + while (bucket != null) { + builder.append("["); + builder.append(bucket.key); + builder.append(","); + builder.append(bucket.value); + builder.append("]"); + bucket = bucket.nextInLookup; + } + + builder.append(")"); + } + + builder.append(")~>("); + + Bucket bucket = firstInSequence; + + while (bucket != null) { + builder.append("["); + builder.append(bucket.key); + builder.append(","); + builder.append(bucket.value); + builder.append("]"); + bucket = bucket.nextInSequence; + } + + builder.append(")<~("); + + bucket = lastInSequence; + + while (bucket != null) { + builder.append("["); + builder.append(bucket.key); + builder.append(","); + builder.append(bucket.value); + builder.append("]"); + bucket = bucket.previousInSequence; + } + + builder.append(")"); + + System.err.println(builder); + } + } diff --git a/core/src/main/ruby/jruby/truffle/core.rb b/core/src/main/ruby/jruby/truffle/core.rb index e18d7d2d7a3..9a2a80c0806 100644 --- a/core/src/main/ruby/jruby/truffle/core.rb +++ b/core/src/main/ruby/jruby/truffle/core.rb @@ -7,6 +7,7 @@ # GNU Lesser General Public License version 2.1 require_relative 'core/main' +require_relative 'core/config' require_relative 'core/kernel' require_relative 'core/float' require_relative 'core/math' diff --git a/core/src/main/ruby/jruby/truffle/core/config.rb b/core/src/main/ruby/jruby/truffle/core/config.rb new file mode 100644 index 00000000000..1481ab99b02 --- /dev/null +++ b/core/src/main/ruby/jruby/truffle/core/config.rb @@ -0,0 +1,17 @@ +# Copyright (c) 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 + +module RbConfig + CONFIG = { + :ruby_install_name => "rubytruffle", + :RUBY_INSTALL_NAME => "rubytruffle", + :host_os => "unknown", + :exeext => "", + :EXEEXT => "rubytruffle", + } +end diff --git a/core/src/main/ruby/jruby/truffle/core/kernel.rb b/core/src/main/ruby/jruby/truffle/core/kernel.rb index ce1d50417e1..1fc086f3edd 100644 --- a/core/src/main/ruby/jruby/truffle/core/kernel.rb +++ b/core/src/main/ruby/jruby/truffle/core/kernel.rb @@ -52,3 +52,13 @@ def Complex(real, imaginary) class Channel end + +# Here temporarily + +class Hash + + def include?(key) + keys.include? key + end + +end diff --git a/spec/truffle/truffle.mspec b/spec/truffle/truffle.mspec index b129acb543d..37cfe492d52 100644 --- a/spec/truffle/truffle.mspec +++ b/spec/truffle/truffle.mspec @@ -46,6 +46,7 @@ class MSpecScript # Can't load these - so tags aren't enough to exclude them + "^spec/ruby/core/array/pack", "^spec/ruby/core/class/dup_spec.rb", "^spec/ruby/core/class/inherited_spec.rb", "^spec/ruby/core/class/new_spec.rb", diff --git a/test/truffle/hash_stress_test.rb b/test/truffle/hash_stress_test.rb new file mode 100644 index 00000000000..160a81bcd2e --- /dev/null +++ b/test/truffle/hash_stress_test.rb @@ -0,0 +1,179 @@ +# Copyright (c) 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 + +# Truffle doesn't have a compliant random number generator yet + +SMALL_RANDOM = [ + 5, 7, 1, 8, 3, + 3, 2, 9, 1, 5, + 2, 6, 4, 3, 3, + 4, 7, 3, 6, 9, + 6, 6, 3, 1, 2, + 4, 1, 8, 5, 7, + 2, 7, 0, 6, 9, + 6, 0, 3, 0, 5, + 5, 4, 8, 9, 7, + 1, 6, 8, 7, 5, + 9, 4, 8, 3, 6, + 6, 3, 4, 5, 6, + 3, 5, 8, 0, 3, + 3, 3, 0, 0, 0, + 8, 8, 4, 0, 3, + 6, 4, 3, 6, 6, + 3, 4, 7, 0, 8, + 2, 3, 4, 3, 9, + 5, 7, 0, 1, 4, + 2, 4, 2, 8, 6 +] + +BIG_RANDOM = [ + 18, 82, 58, 32, 94, + 69, 98, 38, 30, 0, + 10, 61, 70, 97, 83, + 44, 72, 15, 3, 44, + 64, 53, 46, 2, 58, + 9, 13, 39, 17, 40, + 35, 28, 20, 81, 7, + 77, 43, 48, 56, 71, + 43, 66, 35, 77, 29, + 35, 73, 9, 89, 26, + 54, 93, 1, 14, 9, + 30, 77, 81, 75, 62, + 79, 55, 58, 77, 8, + 74, 4, 18, 31, 1, + 82, 90, 34, 53, 21, + 99, 82, 54, 26, 43, + 35, 65, 35, 45, 84, + 66, 27, 7, 1, 79, + 20, 54, 78, 20, 73, + 49, 5, 31, 61, 0 +] + +@small_random = 0 + +def small_random + SMALL_RANDOM[(@small_random += 1) % SMALL_RANDOM.size] +end + +@big_random = 0 + +def big_random + BIG_RANDOM[(@big_random += 1) % BIG_RANDOM.size] +end + +def random_range(range) + big_random % range +end + +$assert_index = 0 + +def assert(condition) + raise "failure on #{$assert_index}" unless condition + $assert_index += 1 +end + +def random_hash + case random_range(5) + when 0 + {} + when 1 + Hash.new + when 2 + eval("{" + small_random.times.map { |n| "#{small_random} => #{small_random}" }.join(", ") + "}") + when 3 + eval("{" + small_random.times.map { |n| "#{big_random} => #{big_random}" }.join(", ") + "}") + when 4 + eval("{" + big_random.times.map { |n| "#{big_random} => #{big_random}" }.join(", ") + "}") + end +end + +def check_hash(hash) + assert eval(hash.inspect) == hash + assert hash == eval(hash.inspect) + assert hash.keys.size == hash.size + assert hash.keys.uniq == hash.keys +end + +1_000.times do + # Create a hash + + hash = random_hash + + check_hash(hash) + + 100.times do + # Perform a random mutation + + case random_range(7) + when 0 + # Clear + hash.clear + assert hash.size == 0 + when 1 + # Merge with a new hash creating a new hash + original_size = hash.size + hash = hash.merge(random_hash) + assert hash.size >= original_size + when 2 + # Set a big random key + original_size = hash.size + key = big_random + value = big_random + hash[key] = value + assert hash[key] == value + assert (hash.size == original_size) || (hash.size == original_size + 1) + when 3 + # Set a small random key + original_size = hash.size + key = small_random + value = small_random + hash[key] = value + assert hash[key] == value + assert (hash.size == original_size) || (hash.size == original_size + 1) + when 4 + # Delete a big random key - if it exists + original_size = hash.size + hash.delete(big_random) + assert (hash.size == original_size) || (hash.size == original_size - 1) + when 5 + # Delete a small random key - if it exists + original_size = hash.size + hash.delete(small_random) + assert (hash.size == original_size) || (hash.size == original_size - 1) + when 6 + # Delete a random one of the actual keys + if hash.size > 0 + original_size = hash.size + hash.delete(hash.keys[big_random % hash.keys.size]) + assert (hash.size == original_size - 1) + end + end + + check_hash(hash) + end +end + +1_000.times do + # Create a hash + + hash = {} + + # Add random elements, remembering the order we put them in + + keys = [] + + 100.times do + key = big_random + keys << key + hash[key] = big_random + end + + # Check the order we get out is the same + + assert hash.keys == keys.uniq +end From 0eee052372897c22764f51e1d38260f5534c6358 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 04:49:08 +0000 Subject: [PATCH 02/12] [Truffle] Fix default block in buckets hash. --- .../jruby/truffle/nodes/core/HashNodes.java | 19 +++++++++++++++---- spec/truffle/truffle.mspec | 1 - 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 4b61f0df32d..41e4e4fc9c7 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -251,16 +251,27 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { } @Specialization(guards = "isBucketArray") - public Object getBucketArray(RubyHash hash, Object key) { + public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); final RubyHash.BucketAndIndex bucketAndIndex = hash.verySlowFindBucket(key); - if (bucketAndIndex.getBucket() == null) { - return getContext().getCoreLibrary().getNilObject(); + if (bucketAndIndex.getBucket() != null) { + return bucketAndIndex.getBucket().value; + } + + notInHashProfile.enter(); + + if (hash.getDefaultBlock() != null) { + useDefaultProfile.enter(); + return yield.dispatch(frame, hash.getDefaultBlock(), hash, key); } - return bucketAndIndex.getBucket().value; + if (hash.getDefaultValue() != null) { + return hash.getDefaultValue(); + } + + return getContext().getCoreLibrary().getNilObject(); } } diff --git a/spec/truffle/truffle.mspec b/spec/truffle/truffle.mspec index 37cfe492d52..b129acb543d 100644 --- a/spec/truffle/truffle.mspec +++ b/spec/truffle/truffle.mspec @@ -46,7 +46,6 @@ class MSpecScript # Can't load these - so tags aren't enough to exclude them - "^spec/ruby/core/array/pack", "^spec/ruby/core/class/dup_spec.rb", "^spec/ruby/core/class/inherited_spec.rb", "^spec/ruby/core/class/new_spec.rb", From 3e82545ea04ce156df539519881d65d0029fd32d Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 05:06:40 +0000 Subject: [PATCH 03/12] [Truffle] Pull out some hash classes to the top level. --- .../jruby/truffle/nodes/core/HashGuards.java | 5 +- .../jruby/truffle/nodes/core/HashNodes.java | 84 +++++------ .../jruby/truffle/nodes/core/KernelNodes.java | 3 +- .../jruby/truffle/nodes/core/SystemNode.java | 5 +- .../nodes/literal/HashLiteralNode.java | 5 +- .../methods/arguments/CheckArityNode.java | 5 +- .../arguments/ReadKeywordArgumentNode.java | 9 +- .../ReadKeywordRestArgumentNode.java | 7 +- .../truffle/runtime/core/CoreLibrary.java | 6 +- .../jruby/truffle/runtime/core/RubyHash.java | 134 +++++------------- .../jruby/truffle/runtime/hash/Bucket.java | 76 ++++++++++ .../runtime/hash/BucketSearchResult.java | 36 +++++ .../org/jruby/truffle/runtime/hash/Entry.java | 30 ++++ 13 files changed, 243 insertions(+), 162 deletions(-) create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java index 698698be7a6..d48f47a0efd 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java @@ -10,6 +10,7 @@ package org.jruby.truffle.nodes.core; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Bucket; public class HashGuards { @@ -19,11 +20,11 @@ public static boolean isNull(RubyHash hash) { public static boolean isObjectArray(RubyHash hash) { // Arrays are covariant in Java! - return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof RubyHash.Bucket[]); + return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof Bucket[]); } public static boolean isBucketArray(RubyHash hash) { - return hash.getStore() instanceof RubyHash.Bucket[]; + return hash.getStore() instanceof Bucket[]; } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 41e4e4fc9c7..634b7ee2a81 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -16,7 +16,6 @@ import com.oracle.truffle.api.frame.*; import com.oracle.truffle.api.utilities.BranchProfile; import org.jruby.runtime.Visibility; -import org.jruby.truffle.nodes.RubyNode; import org.jruby.truffle.nodes.RubyRootNode; import org.jruby.truffle.nodes.dispatch.DispatchHeadNode; import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; @@ -25,6 +24,9 @@ import org.jruby.truffle.runtime.core.*; import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Bucket; +import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.Entry; import java.util.ArrayList; import java.util.Arrays; @@ -57,8 +59,8 @@ public boolean equalNull(RubyHash a, RubyHash b) { public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { notDesignedForCompilation(); - final List aEntries = a.verySlowToEntries(); - final List bEntries = a.verySlowToEntries(); + final List aEntries = a.verySlowToEntries(); + final List bEntries = a.verySlowToEntries(); if (aEntries.size() != bEntries.size()) { return false; @@ -68,7 +70,7 @@ public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { final boolean[] bUsed = new boolean[bEntries.size()]; - for (RubyHash.Entry aEntry : aEntries) { + for (Entry aEntry : aEntries) { boolean found = false; for (int n = 0; n < bEntries.size(); n++) { @@ -177,10 +179,10 @@ public RubyHash construct(Object[] args) { } else { keyValues.enter(); - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (int n = 0; n < args.length; n += 2) { - entries.add(new RubyHash.Entry(args[n], args[n + 1])); + entries.add(new Entry(args[n], args[n + 1])); } return RubyHash.verySlowFromEntries(getContext(), entries); @@ -254,10 +256,10 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final RubyHash.BucketAndIndex bucketAndIndex = hash.verySlowFindBucket(key); + final BucketSearchResult bucketSearchResult = hash.verySlowFindBucket(key); - if (bucketAndIndex.getBucket() != null) { - return bucketAndIndex.getBucket().value; + if (bucketSearchResult.getBucket() != null) { + return bucketSearchResult.getBucket().getValue(); } notInHashProfile.enter(); @@ -335,11 +337,11 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje // TODO(CS): need to watch for that transfer until we make the following fast path - final List entries = hash.verySlowToEntries(); + final List entries = hash.verySlowToEntries(); - hash.setStore(new RubyHash.Bucket[RubyHash.capacityGreaterThan(newSize)], newSize, null, null); + hash.setStore(new Bucket[RubyHash.capacityGreaterThan(newSize)], newSize, null, null); - for (RubyHash.Entry entry : entries) { + for (Entry entry : entries) { hash.verySlowSetInBuckets(entry.getKey(), entry.getValue()); } @@ -433,43 +435,43 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object delete(RubyHash hash, Object key) { notDesignedForCompilation(); - final RubyHash.BucketAndIndex bucketAndIndex = hash.verySlowFindBucket(key); + final BucketSearchResult bucketSearchResult = hash.verySlowFindBucket(key); - if (bucketAndIndex.getBucket() == null) { + if (bucketSearchResult.getBucket() == null) { return getContext().getCoreLibrary().getNilObject(); } - final RubyHash.Bucket bucket = bucketAndIndex.getBucket(); + final Bucket bucket = bucketSearchResult.getBucket(); // Remove from the sequence chain - if (bucket.previousInSequence == null) { - hash.firstInSequence = bucket.nextInSequence; + if (bucket.getPreviousInSequence() == null) { + hash.firstInSequence = bucket.getNextInSequence(); } else { - bucket.previousInSequence.nextInSequence = bucket.nextInSequence; + bucket.getPreviousInSequence().setNextInSequence(bucket.getNextInSequence()); } - if (bucket.nextInSequence == null) { - hash.lastInSequence = bucket.previousInSequence; + if (bucket.getNextInSequence() == null) { + hash.lastInSequence = bucket.getPreviousInSequence(); } else { - bucket.nextInSequence.previousInSequence = bucket.previousInSequence; + bucket.getNextInSequence().setPreviousInSequence(bucket.getPreviousInSequence()); } // Remove from the lookup chain - if (bucket.previousInLookup == null) { - ((RubyHash.Bucket[]) hash.getStore())[bucketAndIndex.getIndex()] = bucket.nextInLookup; + if (bucket.getPreviousInLookup() == null) { + ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket.getNextInLookup(); } else { - bucket.previousInLookup.nextInLookup = bucket.nextInLookup; + bucket.getPreviousInLookup().setNextInLookup(bucket.getNextInLookup()); } - if (bucket.nextInLookup != null) { - bucket.nextInLookup.previousInLookup = bucket.previousInLookup; + if (bucket.getNextInLookup() != null) { + bucket.getNextInLookup().setPreviousInLookup(bucket.getPreviousInLookup()); } hash.setStoreSize(hash.getStoreSize() - 1); - return bucket.value; + return bucket.getValue(); } } @@ -526,7 +528,7 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc public RubyHash eachBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue())); } @@ -681,7 +683,7 @@ public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) { builder.append("{"); - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { if (builder.length() > 1) { builder.append(", "); } @@ -740,7 +742,7 @@ public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public boolean keyBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { if (eqlNode.call(frame, entry.getKey(), "eql?", null, key)) { return true; } @@ -788,13 +790,13 @@ public RubyArray keysBucketArray(RubyHash hash) { final Object[] keys = new Object[hash.getStoreSize()]; - RubyHash.Bucket bucket = hash.firstInSequence; + Bucket bucket = hash.firstInSequence; int n = 0; while (bucket != null) { - keys[n] = bucket.key; + keys[n] = bucket.getKey(); n++; - bucket = bucket.nextInSequence; + bucket = bucket.getNextInSequence(); } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length); @@ -852,7 +854,7 @@ public RubyArray mapBucketArray(VirtualFrame frame, RubyHash hash, RubyProc bloc final RubyArray array = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { array.slowPush(yield(frame, block, entry.getKey(), entry.getValue())); } @@ -974,13 +976,13 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R @Specialization public RubyHash mergeBucketArrayBucketArray(VirtualFrame frame, RubyHash hash, RubyHash other) { - final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new RubyHash.Bucket[RubyHash.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); + final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[RubyHash.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); } - for (RubyHash.Entry entry : other.verySlowToEntries()) { + for (Entry entry : other.verySlowToEntries()) { merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); } @@ -1083,13 +1085,13 @@ public RubyArray valuesBucketArray(RubyHash hash) { final Object[] values = new Object[hash.getStoreSize()]; - RubyHash.Bucket bucket = hash.firstInSequence; + Bucket bucket = hash.firstInSequence; int n = 0; while (bucket != null) { - values[n] = bucket.value; + values[n] = bucket.getValue(); n++; - bucket = bucket.nextInSequence; + bucket = bucket.getNextInSequence(); } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length); @@ -1139,7 +1141,7 @@ public RubyArray toArrayBucketArray(RubyHash hash) { int n = 0; - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getValue(), entry.getValue()); n++; } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java index a39babb51d4..d65e39cd5c2 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java @@ -37,6 +37,7 @@ import org.jruby.truffle.runtime.core.*; import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Entry; import org.jruby.truffle.runtime.methods.RubyMethod; import org.jruby.util.cli.Options; @@ -596,7 +597,7 @@ private static void exec(RubyContext context, String[] commandLine) { final RubyHash env = context.getCoreLibrary().getENV(); - for (RubyHash.Entry entry : env.verySlowToEntries()) { + for (Entry entry : env.verySlowToEntries()) { builder.environment().put(entry.getKey().toString(), entry.getValue().toString()); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java index ddb5e3e1089..c4504f99283 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java @@ -11,15 +11,14 @@ import java.io.*; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import com.oracle.truffle.api.source.*; import com.oracle.truffle.api.frame.*; import org.jruby.truffle.nodes.*; import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Entry; /** * Represents an expression that is evaluated by running it as a system command via forking and @@ -45,7 +44,7 @@ public Object execute(VirtualFrame frame) { final List envp = new ArrayList<>(); // TODO(CS): cast - for (RubyHash.Entry entry : env.verySlowToEntries()) { + for (Entry entry : env.verySlowToEntries()) { envp.add(entry.getKey().toString() + "=" + entry.getValue().toString()); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java index bc5460f10fd..7c6075a8e79 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java @@ -18,6 +18,7 @@ import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyString; +import org.jruby.truffle.runtime.hash.Entry; import java.util.*; @@ -126,12 +127,12 @@ public GenericHashLiteralNode(RubyContext context, SourceSection sourceSection, public RubyHash executeRubyHash(VirtualFrame frame) { notDesignedForCompilation(); - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (int n = 0; n < keyValues.length; n += 2) { final Object key = keyValues[n].execute(frame); final Object value = keyValues[n + 1].execute(frame); - entries.add(new RubyHash.Entry(key, value)); + entries.add(new Entry(key, value)); } return RubyHash.verySlowFromEntries(getContext(), entries); diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java index 0a159600342..d4f504b9fee 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java @@ -16,10 +16,9 @@ import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.control.RaiseException; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Entry; import org.jruby.truffle.runtime.methods.*; -import java.util.Map; - /** * Check arguments meet the arity of the method. */ @@ -50,7 +49,7 @@ public void executeVoid(VirtualFrame frame) { } if (!keywordsRest && arity.hasKeywords() && getKeywordsHash(frame) != null) { - for (RubyHash.Entry entry : getKeywordsHash(frame).verySlowToEntries()) { + for (Entry entry : getKeywordsHash(frame).verySlowToEntries()) { for (String keyword : keywords) { if (!keyword.toString().equals(entry.getKey().toString())) { throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + entry.getKey().toString(), this)); diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java index 36fdac97308..e38ef2cec91 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java @@ -11,16 +11,11 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import com.oracle.truffle.api.utilities.BranchProfile; import org.jruby.truffle.nodes.RubyNode; -import org.jruby.truffle.nodes.RubyValueProfile; import org.jruby.truffle.runtime.RubyArguments; import org.jruby.truffle.runtime.RubyContext; -import org.jruby.truffle.runtime.UndefinedPlaceholder; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.core.RubyString; - -import java.util.Map; +import org.jruby.truffle.runtime.hash.Entry; public class ReadKeywordArgumentNode extends RubyNode { @@ -47,7 +42,7 @@ public Object execute(VirtualFrame frame) { Object value = null; - for (RubyHash.Entry entry : hash.verySlowToEntries()) { + for (Entry entry : hash.verySlowToEntries()) { if (entry.getKey().toString().equals(name)) { value = entry.getValue(); break; diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java index fdc0c641df7..1b50eb794bd 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java @@ -15,6 +15,7 @@ import org.jruby.truffle.runtime.RubyArguments; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Entry; import java.util.*; @@ -39,16 +40,16 @@ public Object execute(VirtualFrame frame) { return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0, null); } - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); - outer: for (RubyHash.Entry entry : hash.verySlowToEntries()) { + outer: for (Entry entry : hash.verySlowToEntries()) { for (String excludedKeyword : excludedKeywords) { if (excludedKeyword.toString().equals(entry.getKey().toString())) { continue outer; } } - entries.add(new RubyHash.Entry(entry.getKey(), entry.getValue())); + entries.add(new Entry(entry.getKey(), entry.getValue())); } return RubyHash.verySlowFromEntries(getContext(), entries); diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java index babf1c4083e..3e167d8cb30 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java @@ -21,6 +21,7 @@ import org.jruby.truffle.nodes.core.ArrayNodes; import org.jruby.truffle.runtime.RubyCallStack; import org.jruby.truffle.runtime.RubyContext; +import org.jruby.truffle.runtime.hash.Entry; import org.jruby.truffle.runtime.rubinius.RubiniusLibrary; import org.jruby.truffle.translator.TranslatorDriver; import org.jruby.util.cli.Options; @@ -30,7 +31,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -736,10 +736,10 @@ public RubyHash getENV() { public RubyEncoding getDefaultEncoding() { return RubyEncoding.getEncoding(context, "US-ASCII"); } private RubyHash getSystemEnv() { - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (Map.Entry variable : System.getenv().entrySet()) { - entries.add(new RubyHash.Entry(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); + entries.add(new Entry(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); } return RubyHash.verySlowFromEntries(context, entries); diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java index b8b485fb149..964afd54269 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java @@ -15,6 +15,9 @@ import org.jruby.truffle.nodes.RubyNode; import org.jruby.truffle.runtime.DebugOperations; import org.jruby.truffle.runtime.RubyContext; +import org.jruby.truffle.runtime.hash.Bucket; +import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.Entry; import org.jruby.truffle.runtime.subsystems.ObjectSpaceManager; import org.jruby.util.cli.Options; @@ -59,7 +62,7 @@ public RubyBasicObject newInstance(RubyNode currentNode) { public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize, Bucket firstInSequence) { super(rubyClass); - final boolean isObjectArray = store instanceof Object[] && !(store instanceof RubyHash.Bucket[]); + final boolean isObjectArray = store instanceof Object[] && !(store instanceof Bucket[]); assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); @@ -97,7 +100,7 @@ public void setDefaultValue(Object defaultValue) { } public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket lastInSequence) { - final boolean isObjectArray = store instanceof Object[] && !(store instanceof RubyHash.Bucket[]); + final boolean isObjectArray = store instanceof Object[] && !(store instanceof Bucket[]); assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); @@ -120,8 +123,8 @@ public List verySlowToEntries() { Bucket bucket = firstInSequence; while (bucket != null) { - entries.add(new Entry(bucket.key, bucket.value)); - bucket = bucket.nextInSequence; + entries.add(new Entry(bucket.getKey(), bucket.getValue())); + bucket = bucket.getNextInSequence(); } } else if (store instanceof Object[]) { for (int n = 0; n < storeSize; n++) { @@ -136,7 +139,7 @@ public List verySlowToEntries() { @Override public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visitor) { - for (RubyHash.Entry entry : verySlowToEntries()) { + for (Entry entry : verySlowToEntries()) { if (entry.getKey() instanceof RubyBasicObject) { ((RubyBasicObject) entry.getKey()).visitObjectGraph(visitor); } @@ -147,67 +150,6 @@ public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visit } } - public static class BucketAndIndex { - - private final Bucket bucket; - private final int index; - private final Bucket endOfLookupChain; - - public BucketAndIndex(Bucket bucket, int index, Bucket endOfLookupChain) { - this.bucket = bucket; - this.index = index; - this.endOfLookupChain = endOfLookupChain; - } - - public Bucket getBucket() { - return bucket; - } - - public int getIndex() { - return index; - } - - public Bucket getEndOfLookupChain() { - return endOfLookupChain; - } - } - - public static class Entry { - - private final Object key; - private final Object value; - - public Entry(Object key, Object value) { - assert key != null; - assert value != null; - - this.key = key; - this.value = value; - } - - public Object getValue() { - return value; - } - - public Object getKey() { - return key; - } - - } - - public static class Bucket { - - public Object key; - public Object value; - - public Bucket previousInLookup; - public Bucket nextInLookup; - - public Bucket previousInSequence; - public Bucket nextInSequence; - - } - public static int capacityGreaterThan(int size) { for (int capacity : CAPACITIES) { if (capacity > size) { @@ -219,7 +161,7 @@ public static int capacityGreaterThan(int size) { } @CompilerDirectives.SlowPath - public BucketAndIndex verySlowFindBucket(Object key) { + public BucketSearchResult verySlowFindBucket(Object key) { final Bucket[] buckets = (Bucket[]) store; // Hash @@ -251,48 +193,46 @@ public BucketAndIndex verySlowFindBucket(Object key) { while (bucket != null) { // TODO: cast - if ((boolean) DebugOperations.send(getContext(), key, "eql?", null, bucket.key)) { - return new BucketAndIndex(bucket, bucketIndex, bucket); + if ((boolean) DebugOperations.send(getContext(), key, "eql?", null, bucket.getKey())) { + return new BucketSearchResult(bucketIndex, bucket, bucket); } endOfLookupChain = bucket; - bucket = bucket.nextInLookup; + bucket = bucket.getNextInLookup(); } - return new BucketAndIndex(null, bucketIndex, endOfLookupChain); + return new BucketSearchResult(bucketIndex, endOfLookupChain, null); } @CompilerDirectives.SlowPath - public void verySlowSetAtBucket(BucketAndIndex bucketAndIndex, Object key, Object value) { - if (bucketAndIndex.getBucket() == null) { - final Bucket bucket = new RubyHash.Bucket(); - bucket.key = key; - bucket.value = value; + public void verySlowSetAtBucket(BucketSearchResult bucketSearchResult, Object key, Object value) { + if (bucketSearchResult.getBucket() == null) { + final Bucket bucket = new Bucket(key, value); if (firstInSequence == null) { firstInSequence = bucket; lastInSequence = bucket; } else { - lastInSequence.nextInSequence = bucket; - bucket.previousInSequence = lastInSequence; + lastInSequence.setNextInSequence(bucket); + bucket.setPreviousInSequence(lastInSequence); lastInSequence = bucket; } - if (bucketAndIndex.getEndOfLookupChain() == null) { - ((Bucket[]) store)[bucketAndIndex.getIndex()] = bucket; + if (bucketSearchResult.getEndOfLookupChain() == null) { + ((Bucket[]) store)[bucketSearchResult.getIndex()] = bucket; } else { - bucketAndIndex.getEndOfLookupChain().nextInLookup = bucket; - bucket.previousInLookup = bucketAndIndex.getEndOfLookupChain(); + bucketSearchResult.getEndOfLookupChain().setNextInLookup(bucket); + bucket.setPreviousInLookup(bucketSearchResult.getEndOfLookupChain()); } } else { - final Bucket bucket = bucketAndIndex.getBucket(); + final Bucket bucket = bucketSearchResult.getBucket(); // The bucket stays in the same place in the sequence // Update the key (it overwrites even it it's eql?) and value - bucket.key = key; - bucket.value = value; + bucket.setKey(key); + bucket.setValue(value); } } @@ -302,9 +242,9 @@ public boolean verySlowSetInBuckets(Object key, Object value) { key = DebugOperations.send(getContext(), DebugOperations.send(getContext(), key, "dup", null), "freeze", null); } - final BucketAndIndex bucketAndIndex = verySlowFindBucket(key); - verySlowSetAtBucket(bucketAndIndex, key, value); - return bucketAndIndex.getBucket() == null; + final BucketSearchResult bucketSearchResult = verySlowFindBucket(key); + verySlowSetAtBucket(bucketSearchResult, key, value); + return bucketSearchResult.getBucket() == null; } @CompilerDirectives.SlowPath @@ -342,11 +282,11 @@ public void dump() { while (bucket != null) { builder.append("["); - builder.append(bucket.key); + builder.append(bucket.getKey()); builder.append(","); - builder.append(bucket.value); + builder.append(bucket.getValue()); builder.append("]"); - bucket = bucket.nextInLookup; + bucket = bucket.getNextInLookup(); } builder.append(")"); @@ -358,11 +298,11 @@ public void dump() { while (bucket != null) { builder.append("["); - builder.append(bucket.key); + builder.append(bucket.getKey()); builder.append(","); - builder.append(bucket.value); + builder.append(bucket.getValue()); builder.append("]"); - bucket = bucket.nextInSequence; + bucket = bucket.getNextInSequence(); } builder.append(")<~("); @@ -371,11 +311,11 @@ public void dump() { while (bucket != null) { builder.append("["); - builder.append(bucket.key); + builder.append(bucket.getKey()); builder.append(","); - builder.append(bucket.value); + builder.append(bucket.getValue()); builder.append("]"); - bucket = bucket.previousInSequence; + bucket = bucket.getPreviousInSequence(); } builder.append(")"); diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java new file mode 100644 index 00000000000..812c20e3e4b --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 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.hash; + +public class Bucket { + + private Object key; + private Object value; + + private Bucket previousInLookup; + private Bucket nextInLookup; + + private Bucket previousInSequence; + private Bucket nextInSequence; + + public Bucket(Object key, Object value) { + this.key = key; + this.value = value; + } + + public Object getKey() { + return key; + } + + public void setKey(Object key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public Bucket getPreviousInLookup() { + return previousInLookup; + } + + public void setPreviousInLookup(Bucket previousInLookup) { + this.previousInLookup = previousInLookup; + } + + public Bucket getNextInLookup() { + return nextInLookup; + } + + public void setNextInLookup(Bucket nextInLookup) { + this.nextInLookup = nextInLookup; + } + + public Bucket getPreviousInSequence() { + return previousInSequence; + } + + public void setPreviousInSequence(Bucket previousInSequence) { + this.previousInSequence = previousInSequence; + } + + public Bucket getNextInSequence() { + return nextInSequence; + } + + public void setNextInSequence(Bucket nextInSequence) { + this.nextInSequence = nextInSequence; + } + +} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java new file mode 100644 index 00000000000..eb45441b075 --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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.hash; + +public class BucketSearchResult { + + private final Bucket endOfLookupChain; + private final Bucket bucket; + private final int index; + + public BucketSearchResult(int index, Bucket endOfLookupChain, Bucket bucket) { + this.index = index; + this.endOfLookupChain = endOfLookupChain; + this.bucket = bucket; + } + + public int getIndex() { + return index; + } + + public Bucket getEndOfLookupChain() { + return endOfLookupChain; + } + + public Bucket getBucket() { + return bucket; + } + +} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java new file mode 100644 index 00000000000..5f7859d0548 --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 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.hash; + +public class Entry { + + private final Object key; + private final Object value; + + public Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + +} From 579881f0be2050b41372b2771484d3ab4a30a5a4 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 05:29:32 +0000 Subject: [PATCH 04/12] [Truffle] Tidy up RubyHash. --- .../jruby/truffle/nodes/core/HashNodes.java | 89 +++--- .../jruby/truffle/nodes/core/KernelNodes.java | 3 +- .../jruby/truffle/nodes/core/SystemNode.java | 3 +- .../nodes/literal/HashLiteralNode.java | 7 +- .../methods/arguments/CheckArityNode.java | 3 +- .../arguments/ReadKeywordArgumentNode.java | 3 +- .../ReadKeywordRestArgumentNode.java | 5 +- .../truffle/runtime/core/CoreLibrary.java | 3 +- .../jruby/truffle/runtime/core/RubyHash.java | 266 ++---------------- .../truffle/runtime/hash/HashOperations.java | 224 +++++++++++++++ 10 files changed, 313 insertions(+), 293 deletions(-) create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 634b7ee2a81..6dac4dcf409 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -27,6 +27,7 @@ import org.jruby.truffle.runtime.hash.Bucket; import org.jruby.truffle.runtime.hash.BucketSearchResult; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import java.util.ArrayList; import java.util.Arrays; @@ -59,8 +60,8 @@ public boolean equalNull(RubyHash a, RubyHash b) { public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { notDesignedForCompilation(); - final List aEntries = a.verySlowToEntries(); - final List bEntries = a.verySlowToEntries(); + final List aEntries = HashOperations.verySlowToEntries(a); + final List bEntries = HashOperations.verySlowToEntries(a); if (aEntries.size() != bEntries.size()) { return false; @@ -134,13 +135,13 @@ public RubyHash construct(Object[] args) { // TODO(CS): zero length arrays might be a good specialisation - if (store.length <= RubyHash.HASHES_SMALL) { + if (store.length <= HashOperations.SMALL_HASH_SIZE) { smallObjectArray.enter(); final int size = store.length; - final Object[] newStore = new Object[RubyHash.HASHES_SMALL * 2]; + final Object[] newStore = new Object[HashOperations.SMALL_HASH_SIZE * 2]; - for (int n = 0; n < RubyHash.HASHES_SMALL; n++) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size) { final Object pair = store[n]; @@ -185,7 +186,7 @@ public RubyHash construct(Object[] args) { entries.add(new Entry(args[n], args[n + 1])); } - return RubyHash.verySlowFromEntries(getContext(), entries); + return HashOperations.verySlowFromEntries(getContext(), entries); } } @@ -231,7 +232,7 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getStoreSize(); - for (int n = 0; n < RubyHash.HASHES_SMALL; n++) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size && eqlNode.call(frame, store[n * 2], "eql?", null, key)) { return store[n * 2 + 1]; } @@ -256,7 +257,7 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = hash.verySlowFindBucket(key); + final BucketSearchResult bucketSearchResult = HashOperations.verySlowFindBucket(hash, key); if (bucketSearchResult.getBucket() != null) { return bucketSearchResult.getBucket().getValue(); @@ -299,7 +300,7 @@ public SetIndexNode(SetIndexNode prev) { @Specialization(guards = "isNull") public Object setNull(RubyHash hash, Object key, Object value) { hash.checkFrozen(this); - final Object[] store = new Object[RubyHash.HASHES_SMALL * 2]; + final Object[] store = new Object[HashOperations.SMALL_HASH_SIZE * 2]; store[0] = key; store[1] = value; hash.setStore(store, 1, null, null); @@ -314,7 +315,7 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje final Object[] store = (Object[]) hash.getStore(); final int size = hash.getStoreSize(); - for (int n = 0; n < RubyHash.HASHES_SMALL; n++) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size && eqlNode.call(frame, store[n * 2], "eql?", null, key)) { store[n * 2 + 1] = value; return value; @@ -325,7 +326,7 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje final int newSize = size + 1; - if (newSize <= RubyHash.HASHES_SMALL) { + if (newSize <= HashOperations.SMALL_HASH_SIZE) { extendProfile.enter(); store[size * 2] = key; store[size * 2 + 1] = value; @@ -337,15 +338,15 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje // TODO(CS): need to watch for that transfer until we make the following fast path - final List entries = hash.verySlowToEntries(); + final List entries = HashOperations.verySlowToEntries(hash); - hash.setStore(new Bucket[RubyHash.capacityGreaterThan(newSize)], newSize, null, null); + hash.setStore(new Bucket[HashOperations.capacityGreaterThan(newSize)], newSize, null, null); for (Entry entry : entries) { - hash.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + HashOperations.verySlowSetInBuckets(hash, entry.getKey(), entry.getValue()); } - hash.verySlowSetInBuckets(key, value); + HashOperations.verySlowSetInBuckets(hash, key, value); return value; } @@ -354,7 +355,7 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje public Object setBucketArray(RubyHash hash, Object key, Object value) { notDesignedForCompilation(); - if (hash.verySlowSetInBuckets(key, value)) { + if (HashOperations.verySlowSetInBuckets(hash, key, value)) { hash.setStoreSize(hash.getStoreSize() + 1); } @@ -415,12 +416,12 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getStoreSize(); - for (int n = 0; n < RubyHash.HASHES_SMALL * 2; n += 2) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE * 2; n += 2) { if (n < size && eqlNode.call(frame, store[n], "eql?", null, key)) { final Object value = store[n + 1]; // Move the later values down - System.arraycopy(store, n + 2, store, n, RubyHash.HASHES_SMALL * 2 - n - 2); + System.arraycopy(store, n + 2, store, n, HashOperations.SMALL_HASH_SIZE * 2 - n - 2); hash.setStoreSize(size - 1); @@ -435,7 +436,7 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object delete(RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = hash.verySlowFindBucket(key); + final BucketSearchResult bucketSearchResult = HashOperations.verySlowFindBucket(hash, key); if (bucketSearchResult.getBucket() == null) { return getContext().getCoreLibrary().getNilObject(); @@ -446,13 +447,13 @@ public Object delete(RubyHash hash, Object key) { // Remove from the sequence chain if (bucket.getPreviousInSequence() == null) { - hash.firstInSequence = bucket.getNextInSequence(); + hash.setFirstInSequence(bucket.getNextInSequence()); } else { bucket.getPreviousInSequence().setNextInSequence(bucket.getNextInSequence()); } if (bucket.getNextInSequence() == null) { - hash.lastInSequence = bucket.getPreviousInSequence(); + hash.setLastInSequence(bucket.getPreviousInSequence()); } else { bucket.getNextInSequence().setPreviousInSequence(bucket.getPreviousInSequence()); } @@ -506,7 +507,7 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc int count = 0; try { - for (int n = 0; n < RubyHash.HASHES_SMALL; n++) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (CompilerDirectives.inInterpreter()) { count++; } @@ -528,7 +529,7 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc public RubyHash eachBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue())); } @@ -631,7 +632,7 @@ public RubyHash dupObjectArray(RubyHash self, RubyHash from) { } final Object[] store = (Object[]) from.getStore(); - self.setStore(Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2), store.length, null, null); + self.setStore(Arrays.copyOf(store, HashOperations.SMALL_HASH_SIZE * 2), store.length, null, null); self.setDefaultBlock(from.getDefaultBlock()); self.setDefaultValue(from.getDefaultValue()); @@ -646,7 +647,7 @@ public RubyHash dupBucketArray(RubyHash self, RubyHash from) { return self; } - self.verySlowSetEntries(from.verySlowToEntries()); + HashOperations.verySlowSetEntries(self, HashOperations.verySlowToEntries(from)); return self; } @@ -683,7 +684,7 @@ public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) { builder.append("{"); - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { if (builder.length() > 1) { builder.append(", "); } @@ -742,7 +743,7 @@ public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public boolean keyBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { if (eqlNode.call(frame, entry.getKey(), "eql?", null, key)) { return true; } @@ -790,7 +791,7 @@ public RubyArray keysBucketArray(RubyHash hash) { final Object[] keys = new Object[hash.getStoreSize()]; - Bucket bucket = hash.firstInSequence; + Bucket bucket = hash.getFirstInSequence(); int n = 0; while (bucket != null) { @@ -828,7 +829,7 @@ public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc int count = 0; try { - for (int n = 0; n < RubyHash.HASHES_SMALL; n++) { + for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size) { final Object key = store[n * 2]; final Object value = store[n * 2 + 1]; @@ -854,7 +855,7 @@ public RubyArray mapBucketArray(VirtualFrame frame, RubyHash hash, RubyProc bloc final RubyArray array = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { array.slowPush(yield(frame, block, entry.getKey(), entry.getValue())); } @@ -874,7 +875,7 @@ public abstract static class MergeNode extends HashCoreMethodNode { private final BranchProfile considerResultIsSmallProfile = new BranchProfile(); private final BranchProfile resultIsSmallProfile = new BranchProfile(); - private final int smallHashSize = RubyHash.HASHES_SMALL; + private final int smallHashSize = HashOperations.SMALL_HASH_SIZE; public MergeNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); @@ -889,7 +890,7 @@ public MergeNode(MergeNode prev) { @Specialization(guards = {"isObjectArray", "isNull(arguments[1])"}) public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) { final Object[] store = (Object[]) hash.getStore(); - final Object[] copy = Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2); + final Object[] copy = Arrays.copyOf(store, HashOperations.SMALL_HASH_SIZE * 2); return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getStoreSize(), null); } @@ -908,11 +909,11 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R final boolean[] mergeFromA = new boolean[storeASize]; int mergeFromACount = 0; - for (int a = 0; a < RubyHash.HASHES_SMALL; a++) { + for (int a = 0; a < HashOperations.SMALL_HASH_SIZE; a++) { if (a < storeASize) { boolean merge = true; - for (int b = 0; b < RubyHash.HASHES_SMALL; b++) { + for (int b = 0; b < HashOperations.SMALL_HASH_SIZE; b++) { if (b < storeBSize) { if (eqlNode.call(frame, storeA[a * 2], "eql?", null, storeB[b * 2])) { merge = false; @@ -931,14 +932,14 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R if (mergeFromACount == 0) { nothingFromFirstProfile.enter(); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize, null); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, HashOperations.SMALL_HASH_SIZE * 2), storeBSize, null); } considerNothingFromSecondProfile.enter(); if (mergeFromACount == storeB.length) { nothingFromSecondProfile.enter(); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize, null); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, HashOperations.SMALL_HASH_SIZE * 2), storeBSize, null); } considerResultIsSmallProfile.enter(); @@ -948,7 +949,7 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R if (storeBSize + mergeFromACount <= smallHashSize) { resultIsSmallProfile.enter(); - final Object[] merged = new Object[RubyHash.HASHES_SMALL * 2]; + final Object[] merged = new Object[HashOperations.SMALL_HASH_SIZE * 2]; int index = 0; @@ -976,14 +977,14 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R @Specialization public RubyHash mergeBucketArrayBucketArray(VirtualFrame frame, RubyHash hash, RubyHash other) { - final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[RubyHash.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); + final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[HashOperations.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); - for (Entry entry : hash.verySlowToEntries()) { - merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + for (Entry entry : HashOperations.verySlowToEntries(hash)) { + HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue()); } - for (Entry entry : other.verySlowToEntries()) { - merged.verySlowSetInBuckets(entry.getKey(), entry.getValue()); + for (Entry entry : HashOperations.verySlowToEntries(other)) { + HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue()); } return merged; @@ -1085,7 +1086,7 @@ public RubyArray valuesBucketArray(RubyHash hash) { final Object[] values = new Object[hash.getStoreSize()]; - Bucket bucket = hash.firstInSequence; + Bucket bucket = hash.getFirstInSequence(); int n = 0; while (bucket != null) { @@ -1141,7 +1142,7 @@ public RubyArray toArrayBucketArray(RubyHash hash) { int n = 0; - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getValue(), entry.getValue()); n++; } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java index d65e39cd5c2..84c82e8d77e 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java @@ -38,6 +38,7 @@ import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.methods.RubyMethod; import org.jruby.util.cli.Options; @@ -597,7 +598,7 @@ private static void exec(RubyContext context, String[] commandLine) { final RubyHash env = context.getCoreLibrary().getENV(); - for (Entry entry : env.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(env)) { builder.environment().put(entry.getKey().toString(), entry.getValue().toString()); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java index c4504f99283..9d58bc1a72f 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java @@ -19,6 +19,7 @@ import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; /** * Represents an expression that is evaluated by running it as a system command via forking and @@ -44,7 +45,7 @@ public Object execute(VirtualFrame frame) { final List envp = new ArrayList<>(); // TODO(CS): cast - for (Entry entry : env.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(env)) { envp.add(entry.getKey().toString() + "=" + entry.getValue().toString()); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java index 7c6075a8e79..f7cc76e7db4 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java @@ -19,6 +19,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyString; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import java.util.*; @@ -39,7 +40,7 @@ protected HashLiteralNode(RubyContext context, SourceSection sourceSection, Ruby public static HashLiteralNode create(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) { if (keyValues.length == 0) { return new EmptyHashLiteralNode(context, sourceSection); - } else if (keyValues.length <= RubyHash.HASHES_SMALL * 2) { + } else if (keyValues.length <= HashOperations.SMALL_HASH_SIZE * 2) { return new SmallHashLiteralNode(context, sourceSection, keyValues); } else { return new GenericHashLiteralNode(context, sourceSection, keyValues); @@ -87,7 +88,7 @@ public SmallHashLiteralNode(RubyContext context, SourceSection sourceSection, Ru @ExplodeLoop @Override public RubyHash executeRubyHash(VirtualFrame frame) { - final Object[] storage = new Object[RubyHash.HASHES_SMALL * 2]; + final Object[] storage = new Object[HashOperations.SMALL_HASH_SIZE * 2]; int position = 0; @@ -135,7 +136,7 @@ public RubyHash executeRubyHash(VirtualFrame frame) { entries.add(new Entry(key, value)); } - return RubyHash.verySlowFromEntries(getContext(), entries); + return HashOperations.verySlowFromEntries(getContext(), entries); } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java index d4f504b9fee..2dccef1cf05 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java @@ -17,6 +17,7 @@ import org.jruby.truffle.runtime.control.RaiseException; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.methods.*; /** @@ -49,7 +50,7 @@ public void executeVoid(VirtualFrame frame) { } if (!keywordsRest && arity.hasKeywords() && getKeywordsHash(frame) != null) { - for (Entry entry : getKeywordsHash(frame).verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(getKeywordsHash(frame))) { for (String keyword : keywords) { if (!keyword.toString().equals(entry.getKey().toString())) { throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + entry.getKey().toString(), this)); diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java index e38ef2cec91..b1a211e15ea 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java @@ -16,6 +16,7 @@ import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; public class ReadKeywordArgumentNode extends RubyNode { @@ -42,7 +43,7 @@ public Object execute(VirtualFrame frame) { Object value = null; - for (Entry entry : hash.verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(hash)) { if (entry.getKey().toString().equals(name)) { value = entry.getValue(); break; diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java index 1b50eb794bd..df944dc432d 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java @@ -16,6 +16,7 @@ import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import java.util.*; @@ -42,7 +43,7 @@ public Object execute(VirtualFrame frame) { final List entries = new ArrayList<>(); - outer: for (Entry entry : hash.verySlowToEntries()) { + outer: for (Entry entry : HashOperations.verySlowToEntries(hash)) { for (String excludedKeyword : excludedKeywords) { if (excludedKeyword.toString().equals(entry.getKey().toString())) { continue outer; @@ -52,7 +53,7 @@ public Object execute(VirtualFrame frame) { entries.add(new Entry(entry.getKey(), entry.getValue())); } - return RubyHash.verySlowFromEntries(getContext(), entries); + return HashOperations.verySlowFromEntries(getContext(), entries); } private RubyHash getKeywordsHash(VirtualFrame frame) { diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java index 3e167d8cb30..de2bf62539b 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java @@ -22,6 +22,7 @@ import org.jruby.truffle.runtime.RubyCallStack; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.rubinius.RubiniusLibrary; import org.jruby.truffle.translator.TranslatorDriver; import org.jruby.util.cli.Options; @@ -742,7 +743,7 @@ private RubyHash getSystemEnv() { entries.add(new Entry(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); } - return RubyHash.verySlowFromEntries(context, entries); + return HashOperations.verySlowFromEntries(context, entries); } public ArrayNodes.MinBlock getArrayMinBlock() { diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java index 964afd54269..e4b1c9cac7a 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java @@ -9,36 +9,15 @@ */ package org.jruby.truffle.runtime.core; -import java.util.*; - -import com.oracle.truffle.api.CompilerDirectives; import org.jruby.truffle.nodes.RubyNode; -import org.jruby.truffle.runtime.DebugOperations; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.hash.Bucket; -import org.jruby.truffle.runtime.hash.BucketSearchResult; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.subsystems.ObjectSpaceManager; -import org.jruby.util.cli.Options; -/** - * Represents the Ruby {@code Hash} class. - */ public class RubyHash extends RubyBasicObject { - // TODO(CS): I think we need to salt the hash somehow - there have been DOS attacks on Ruby for that in the past - - public static final int HASHES_SMALL = Options.TRUFFLE_HASHES_SMALL.load(); - - public static final int[] CAPACITIES = Arrays.copyOf(org.jruby.RubyHash.MRI_PRIMES, org.jruby.RubyHash.MRI_PRIMES.length - 1); - - private static final int SIGN_BIT_MASK = ~(1 << 31); - - /** - * The class from which we create the object that is {@code Hash}. A subclass of - * {@link org.jruby.truffle.runtime.core.RubyClass} so that we can override {@link RubyClass#newInstance} and allocate a - * {@link RubyHash} rather than a normal {@link org.jruby.truffle.runtime.core.RubyBasicObject}. - */ public static class RubyHashClass extends RubyClass { public RubyHashClass(RubyContext context, RubyClass objectClass) { @@ -56,18 +35,11 @@ public RubyBasicObject newInstance(RubyNode currentNode) { private Object defaultValue; private Object store; private int storeSize; - public Bucket firstInSequence; - public Bucket lastInSequence; + private Bucket firstInSequence; + private Bucket lastInSequence; public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize, Bucket firstInSequence) { super(rubyClass); - - final boolean isObjectArray = store instanceof Object[] && !(store instanceof Bucket[]); - assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); - assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); - assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); - assert !isObjectArray || (firstInSequence == null) : store.getClass(); - this.defaultBlock = defaultBlock; this.defaultValue = defaultValue; this.store = store; @@ -79,67 +51,56 @@ public RubyProc getDefaultBlock() { return defaultBlock; } - public Object getDefaultValue() { - return defaultValue; - } - - public Object getStore() { - return store; - } - - public int getStoreSize() { - return storeSize; - } - public void setDefaultBlock(RubyProc defaultBlock) { this.defaultBlock = defaultBlock; } + public Object getDefaultValue() { + return defaultValue; + } + public void setDefaultValue(Object defaultValue) { this.defaultValue = defaultValue; } - public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket lastInSequence) { - final boolean isObjectArray = store instanceof Object[] && !(store instanceof Bucket[]); - assert store == null || store instanceof Object[] || store instanceof Bucket[] : store.getClass(); - assert !isObjectArray || ((Object[]) store).length == HASHES_SMALL * 2 : store.getClass(); - assert !isObjectArray || storeSize <= HASHES_SMALL : store.getClass(); - assert !isObjectArray || (firstInSequence == null) : store.getClass(); + public Object getStore() { + return store; + } + public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket lastInSequence) { this.store = store; this.storeSize = storeSize; this.firstInSequence = firstInSequence; this.lastInSequence = lastInSequence; } + public int getStoreSize() { + return storeSize; + } + public void setStoreSize(int storeSize) { this.storeSize = storeSize; } - public List verySlowToEntries() { - final List entries = new ArrayList<>(); + public Bucket getFirstInSequence() { + return firstInSequence; + } - if (store instanceof Bucket[]) { - Bucket bucket = firstInSequence; + public void setFirstInSequence(Bucket firstInSequence) { + this.firstInSequence = firstInSequence; + } - while (bucket != null) { - entries.add(new Entry(bucket.getKey(), bucket.getValue())); - bucket = bucket.getNextInSequence(); - } - } else if (store instanceof Object[]) { - for (int n = 0; n < storeSize; n++) { - entries.add(new Entry(((Object[]) store)[n * 2], ((Object[]) store)[n * 2 + 1])); - } - } else if (store != null) { - throw new UnsupportedOperationException(); - } + public Bucket getLastInSequence() { + return lastInSequence; + } - return entries; + public void setLastInSequence(Bucket lastInSequence) { + this.lastInSequence = lastInSequence; } @Override public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visitor) { - for (Entry entry : verySlowToEntries()) { + for (Entry entry : HashOperations.verySlowToEntries(this)) { if (entry.getKey() instanceof RubyBasicObject) { ((RubyBasicObject) entry.getKey()).visitObjectGraph(visitor); } @@ -150,177 +111,4 @@ public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visit } } - public static int capacityGreaterThan(int size) { - for (int capacity : CAPACITIES) { - if (capacity > size) { - return capacity; - } - } - - return CAPACITIES[CAPACITIES.length - 1]; - } - - @CompilerDirectives.SlowPath - public BucketSearchResult verySlowFindBucket(Object key) { - final Bucket[] buckets = (Bucket[]) store; - - // Hash - - // TODO: cast - - final Object hashValue = DebugOperations.send(getContext(), key, "hash", null); - - final int hashed; - - if (hashValue instanceof Integer) { - hashed = (int) hashValue; - } else if (hashValue instanceof Long) { - hashed = (int) (long) hashValue; - } else { - throw new UnsupportedOperationException(); - } - - final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; - - // Find the initial bucket - - Bucket bucket = buckets[bucketIndex]; - - // Go through the chain of buckets to see if we're going to overwrite a key or append a new bucket - - Bucket endOfLookupChain = null; - - while (bucket != null) { - // TODO: cast - - if ((boolean) DebugOperations.send(getContext(), key, "eql?", null, bucket.getKey())) { - return new BucketSearchResult(bucketIndex, bucket, bucket); - } - - endOfLookupChain = bucket; - bucket = bucket.getNextInLookup(); - } - - return new BucketSearchResult(bucketIndex, endOfLookupChain, null); - } - - @CompilerDirectives.SlowPath - public void verySlowSetAtBucket(BucketSearchResult bucketSearchResult, Object key, Object value) { - if (bucketSearchResult.getBucket() == null) { - final Bucket bucket = new Bucket(key, value); - - if (firstInSequence == null) { - firstInSequence = bucket; - lastInSequence = bucket; - } else { - lastInSequence.setNextInSequence(bucket); - bucket.setPreviousInSequence(lastInSequence); - lastInSequence = bucket; - } - - if (bucketSearchResult.getEndOfLookupChain() == null) { - ((Bucket[]) store)[bucketSearchResult.getIndex()] = bucket; - } else { - bucketSearchResult.getEndOfLookupChain().setNextInLookup(bucket); - bucket.setPreviousInLookup(bucketSearchResult.getEndOfLookupChain()); - } - } else { - final Bucket bucket = bucketSearchResult.getBucket(); - - // The bucket stays in the same place in the sequence - - // Update the key (it overwrites even it it's eql?) and value - - bucket.setKey(key); - bucket.setValue(value); - } - } - - @CompilerDirectives.SlowPath - public boolean verySlowSetInBuckets(Object key, Object value) { - if (key instanceof RubyString) { - key = DebugOperations.send(getContext(), DebugOperations.send(getContext(), key, "dup", null), "freeze", null); - } - - final BucketSearchResult bucketSearchResult = verySlowFindBucket(key); - verySlowSetAtBucket(bucketSearchResult, key, value); - return bucketSearchResult.getBucket() == null; - } - - @CompilerDirectives.SlowPath - public static RubyHash verySlowFromEntries(RubyContext context, List entries) { - RubyNode.notDesignedForCompilation(); - - final RubyHash hash = new RubyHash(context.getCoreLibrary().getHashClass(), null, null, null, 0, null); - hash.verySlowSetEntries(entries); - return hash; - } - - @CompilerDirectives.SlowPath - public void verySlowSetEntries(List entries) { - final int size = entries.size(); - setStore(new Bucket[RubyHash.capacityGreaterThan(size)], 0, null, null); - - int actualSize = 0; - - for (Entry entry : entries) { - if (verySlowSetInBuckets(entry.getKey(), entry.getValue())) { - actualSize++; - } - } - - setStoreSize(actualSize); - } - - public void dump() { - final StringBuilder builder = new StringBuilder(); - - builder.append("("); - - for (Bucket bucket : (Bucket[]) store) { - builder.append("("); - - while (bucket != null) { - builder.append("["); - builder.append(bucket.getKey()); - builder.append(","); - builder.append(bucket.getValue()); - builder.append("]"); - bucket = bucket.getNextInLookup(); - } - - builder.append(")"); - } - - builder.append(")~>("); - - Bucket bucket = firstInSequence; - - while (bucket != null) { - builder.append("["); - builder.append(bucket.getKey()); - builder.append(","); - builder.append(bucket.getValue()); - builder.append("]"); - bucket = bucket.getNextInSequence(); - } - - builder.append(")<~("); - - bucket = lastInSequence; - - while (bucket != null) { - builder.append("["); - builder.append(bucket.getKey()); - builder.append(","); - builder.append(bucket.getValue()); - builder.append("]"); - bucket = bucket.getPreviousInSequence(); - } - - builder.append(")"); - - System.err.println(builder); - } - } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java new file mode 100644 index 00000000000..a7ba220c192 --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 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.hash; + +import com.oracle.truffle.api.CompilerDirectives; +import org.jruby.truffle.nodes.RubyNode; +import org.jruby.truffle.runtime.DebugOperations; +import org.jruby.truffle.runtime.RubyContext; +import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.core.RubyString; +import org.jruby.util.cli.Options; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class HashOperations { + + public static final int SMALL_HASH_SIZE = Options.TRUFFLE_HASHES_SMALL.load(); + public static final int[] CAPACITIES = Arrays.copyOf(org.jruby.RubyHash.MRI_PRIMES, org.jruby.RubyHash.MRI_PRIMES.length - 1); + public static final int SIGN_BIT_MASK = ~(1 << 31); + + public static int capacityGreaterThan(int size) { + for (int capacity : CAPACITIES) { + if (capacity > size) { + return capacity; + } + } + + return CAPACITIES[CAPACITIES.length - 1]; + } + + @CompilerDirectives.SlowPath + public static RubyHash verySlowFromEntries(RubyContext context, List entries) { + RubyNode.notDesignedForCompilation(); + + final RubyHash hash = new RubyHash(context.getCoreLibrary().getHashClass(), null, null, null, 0, null); + verySlowSetEntries(hash, entries); + return hash; + } + + public static void dump(RubyHash hash) { + final StringBuilder builder = new StringBuilder(); + + builder.append("("); + + for (Bucket bucket : (Bucket[]) hash.getStore()) { + builder.append("("); + + while (bucket != null) { + builder.append("["); + builder.append(bucket.getKey()); + builder.append(","); + builder.append(bucket.getValue()); + builder.append("]"); + bucket = bucket.getNextInLookup(); + } + + builder.append(")"); + } + + builder.append(")~>("); + + Bucket bucket = hash.getFirstInSequence(); + + while (bucket != null) { + builder.append("["); + builder.append(bucket.getKey()); + builder.append(","); + builder.append(bucket.getValue()); + builder.append("]"); + bucket = bucket.getNextInSequence(); + } + + builder.append(")<~("); + + bucket = hash.getLastInSequence(); + + while (bucket != null) { + builder.append("["); + builder.append(bucket.getKey()); + builder.append(","); + builder.append(bucket.getValue()); + builder.append("]"); + bucket = bucket.getPreviousInSequence(); + } + + builder.append(")"); + + System.err.println(builder); + } + + @CompilerDirectives.SlowPath + public static List verySlowToEntries(RubyHash hash) { + final List entries = new ArrayList<>(); + + if (hash.getStore() instanceof Bucket[]) { + Bucket bucket = hash.getFirstInSequence(); + + while (bucket != null) { + entries.add(new Entry(bucket.getKey(), bucket.getValue())); + bucket = bucket.getNextInSequence(); + } + } else if (hash.getStore() instanceof Object[]) { + for (int n = 0; n < hash.getStoreSize(); n++) { + entries.add(new Entry(((Object[]) hash.getStore())[n * 2], ((Object[]) hash.getStore())[n * 2 + 1])); + } + } else if (hash.getStore() != null) { + throw new UnsupportedOperationException(); + } + + return entries; + } + + @CompilerDirectives.SlowPath + public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { + final Bucket[] buckets = (Bucket[]) hash.getStore(); + + // Hash + + // TODO: cast + + final Object hashValue = DebugOperations.send(hash.getContext(), key, "hash", null); + + final int hashed; + + if (hashValue instanceof Integer) { + hashed = (int) hashValue; + } else if (hashValue instanceof Long) { + hashed = (int) (long) hashValue; + } else { + throw new UnsupportedOperationException(); + } + + final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; + + // Find the initial bucket + + Bucket bucket = buckets[bucketIndex]; + + // Go through the chain of buckets to see if we're going to overwrite a key or append a new bucket + + Bucket endOfLookupChain = null; + + while (bucket != null) { + // TODO: cast + + if ((boolean) DebugOperations.send(hash.getContext(), key, "eql?", null, bucket.getKey())) { + return new BucketSearchResult(bucketIndex, bucket, bucket); + } + + endOfLookupChain = bucket; + bucket = bucket.getNextInLookup(); + } + + return new BucketSearchResult(bucketIndex, endOfLookupChain, null); + } + + @CompilerDirectives.SlowPath + public static void verySlowSetAtBucket(RubyHash hash, BucketSearchResult bucketSearchResult, Object key, Object value) { + if (bucketSearchResult.getBucket() == null) { + final Bucket bucket = new Bucket(key, value); + + if (hash.getFirstInSequence() == null) { + hash.setFirstInSequence(bucket); + hash.setLastInSequence(bucket); + } else { + hash.getLastInSequence().setNextInSequence(bucket); + bucket.setPreviousInSequence(hash.getLastInSequence()); + hash.setLastInSequence(bucket); + } + + if (bucketSearchResult.getEndOfLookupChain() == null) { + ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket; + } else { + bucketSearchResult.getEndOfLookupChain().setNextInLookup(bucket); + bucket.setPreviousInLookup(bucketSearchResult.getEndOfLookupChain()); + } + } else { + final Bucket bucket = bucketSearchResult.getBucket(); + + // The bucket stays in the same place in the sequence + + // Update the key (it overwrites even it it's eql?) and value + + bucket.setKey(key); + bucket.setValue(value); + } + } + + @CompilerDirectives.SlowPath + public static boolean verySlowSetInBuckets(RubyHash hash, Object key, Object value) { + if (key instanceof RubyString) { + key = DebugOperations.send(hash.getContext(), DebugOperations.send(hash.getContext(), key, "dup", null), "freeze", null); + } + + final BucketSearchResult bucketSearchResult = verySlowFindBucket(hash, key); + verySlowSetAtBucket(hash, bucketSearchResult, key, value); + return bucketSearchResult.getBucket() == null; + } + + @CompilerDirectives.SlowPath + public static void verySlowSetEntries(RubyHash hash, List entries) { + final int size = entries.size(); + hash.setStore(new Bucket[capacityGreaterThan(size)], 0, null, null); + + int actualSize = 0; + + for (Entry entry : entries) { + if (verySlowSetInBuckets(hash, entry.getKey(), entry.getValue())) { + actualSize++; + } + } + + hash.setStoreSize(actualSize); + } +} From 4b6b34ad74d47ecd7daa45b1db7200cc82430853 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 05:51:00 +0000 Subject: [PATCH 05/12] [Truffle] Make finding a bucket a node. --- .../org/jruby/truffle/nodes/RubyNode.java | 5 ++ .../org/jruby/truffle/nodes/RubyTypes.java | 2 + .../jruby/truffle/nodes/core/HashNodes.java | 13 +++- .../truffle/nodes/hash/FindBucketNode.java | 70 +++++++++++++++++++ .../truffle/runtime/hash/HashOperations.java | 17 +---- 5 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java index 00893d12182..1bafcfa6c0a 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java @@ -24,6 +24,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyRange; import org.jruby.truffle.runtime.core.RubyBasicObject; +import org.jruby.truffle.runtime.hash.BucketSearchResult; import org.jruby.truffle.runtime.rubinius.RubiniusByteArray; import org.jruby.truffle.runtime.rubinius.RubiniusChannel; @@ -191,6 +192,10 @@ public RubyEncodingConverter executeRubyEncodingConverter(VirtualFrame frame) th return RubyTypesGen.RUBYTYPES.expectRubyEncodingConverter(execute(frame)); } + public BucketSearchResult executeBucketSearchResult(VirtualFrame frame) throws UnexpectedResultException { + return RubyTypesGen.RUBYTYPES.expectBucketSearchResult(execute(frame)); + } + public Dispatch.DispatchAction executeDispatchAction(VirtualFrame frame) { throw new UnsupportedOperationException(); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java index dcd562f55a2..5faf3c5a4f7 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java @@ -18,6 +18,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyRange; import org.jruby.truffle.runtime.core.RubyBasicObject; +import org.jruby.truffle.runtime.hash.BucketSearchResult; import org.jruby.truffle.runtime.rubinius.RubiniusByteArray; import org.jruby.truffle.runtime.rubinius.RubiniusChannel; import org.jruby.truffle.runtime.LexicalScope; @@ -61,6 +62,7 @@ RubiniusByteArray.class, // RubyEncodingConverter.class, // RubyBasicObject.class, // + BucketSearchResult.class, // Object[].class}) public class RubyTypes { diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 6dac4dcf409..a2c9ef81336 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -19,6 +19,7 @@ import org.jruby.truffle.nodes.RubyRootNode; import org.jruby.truffle.nodes.dispatch.DispatchHeadNode; import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; +import org.jruby.truffle.nodes.hash.FindBucketNode; import org.jruby.truffle.nodes.yield.YieldDispatchHeadNode; import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.*; @@ -197,6 +198,7 @@ public abstract static class GetIndexNode extends HashCoreMethodNode { @Child protected PredicateDispatchHeadNode eqlNode; @Child protected YieldDispatchHeadNode yield; + @Child protected FindBucketNode findBucketNode; private final BranchProfile notInHashProfile = new BranchProfile(); private final BranchProfile useDefaultProfile = new BranchProfile(); @@ -205,12 +207,14 @@ public GetIndexNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); eqlNode = new PredicateDispatchHeadNode(context); yield = new YieldDispatchHeadNode(context); + findBucketNode = new FindBucketNode(context, sourceSection); } public GetIndexNode(GetIndexNode prev) { super(prev); eqlNode = prev.eqlNode; yield = prev.yield; + findBucketNode = prev.findBucketNode; } @Specialization(guards = "isNull") @@ -257,7 +261,7 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = HashOperations.verySlowFindBucket(hash, key); + final BucketSearchResult bucketSearchResult = findBucketNode.search(frame, hash, key); if (bucketSearchResult.getBucket() != null) { return bucketSearchResult.getBucket().getValue(); @@ -392,15 +396,18 @@ public RubyHash empty(RubyHash hash) { public abstract static class DeleteNode extends HashCoreMethodNode { @Child protected PredicateDispatchHeadNode eqlNode; + @Child protected FindBucketNode findBucketNode; public DeleteNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); eqlNode = new PredicateDispatchHeadNode(context); + findBucketNode = new FindBucketNode(context, sourceSection); } public DeleteNode(DeleteNode prev) { super(prev); eqlNode = prev.eqlNode; + findBucketNode = prev.findBucketNode; } @Specialization(guards = "isNull") @@ -433,10 +440,10 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { } @Specialization(guards = "isBucketArray") - public Object delete(RubyHash hash, Object key) { + public Object delete(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = HashOperations.verySlowFindBucket(hash, key); + final BucketSearchResult bucketSearchResult = findBucketNode.search(frame, hash, key); if (bucketSearchResult.getBucket() == null) { return getContext().getCoreLibrary().getNilObject(); diff --git a/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java b/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java new file mode 100644 index 00000000000..2dea0ec6d6f --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 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.nodes.hash; + +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.source.SourceSection; +import org.jruby.truffle.nodes.RubyNode; +import org.jruby.truffle.nodes.dispatch.DispatchHeadNode; +import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; +import org.jruby.truffle.runtime.RubyContext; +import org.jruby.truffle.runtime.core.RubyHash; +import org.jruby.truffle.runtime.hash.Bucket; +import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.HashOperations; + +public class FindBucketNode extends RubyNode { + + @Child DispatchHeadNode hashNode; + @Child PredicateDispatchHeadNode eqlNode; + + public FindBucketNode(RubyContext context, SourceSection sourceSection) { + super(context, sourceSection); + hashNode = new DispatchHeadNode(context); + eqlNode = new PredicateDispatchHeadNode(context); + } + + public BucketSearchResult search(VirtualFrame frame, RubyHash hash, Object key) { + final Object hashValue = hashNode.call(frame, key, "hash", null); + + final int hashed; + + if (hashValue instanceof Integer) { + hashed = (int) hashValue; + } else if (hashValue instanceof Long) { + hashed = (int) (long) hashValue; + } else { + throw new UnsupportedOperationException(); + } + + final Bucket[] buckets = (Bucket[]) hash.getStore(); + final int bucketIndex = (hashed & HashOperations.SIGN_BIT_MASK) % buckets.length; + Bucket bucket = buckets[bucketIndex]; + + Bucket endOfLookupChain = null; + + while (bucket != null) { + if (eqlNode.call(frame, key, "eql?", null, bucket.getKey())) { + return new BucketSearchResult(bucketIndex, bucket, bucket); + } + + endOfLookupChain = bucket; + bucket = bucket.getNextInLookup(); + } + + return new BucketSearchResult(bucketIndex, endOfLookupChain, null); + } + + @Override + public Object execute(VirtualFrame frame) { + throw new UnsupportedOperationException(); + } + +} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java index a7ba220c192..e5c474fe874 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java @@ -121,12 +121,6 @@ public static List verySlowToEntries(RubyHash hash) { @CompilerDirectives.SlowPath public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { - final Bucket[] buckets = (Bucket[]) hash.getStore(); - - // Hash - - // TODO: cast - final Object hashValue = DebugOperations.send(hash.getContext(), key, "hash", null); final int hashed; @@ -139,14 +133,10 @@ public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { throw new UnsupportedOperationException(); } + final Bucket[] buckets = (Bucket[]) hash.getStore(); final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; - - // Find the initial bucket - Bucket bucket = buckets[bucketIndex]; - // Go through the chain of buckets to see if we're going to overwrite a key or append a new bucket - Bucket endOfLookupChain = null; while (bucket != null) { @@ -163,8 +153,7 @@ public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { return new BucketSearchResult(bucketIndex, endOfLookupChain, null); } - @CompilerDirectives.SlowPath - public static void verySlowSetAtBucket(RubyHash hash, BucketSearchResult bucketSearchResult, Object key, Object value) { + public static void setAtBucket(RubyHash hash, BucketSearchResult bucketSearchResult, Object key, Object value) { if (bucketSearchResult.getBucket() == null) { final Bucket bucket = new Bucket(key, value); @@ -202,7 +191,7 @@ public static boolean verySlowSetInBuckets(RubyHash hash, Object key, Object val } final BucketSearchResult bucketSearchResult = verySlowFindBucket(hash, key); - verySlowSetAtBucket(hash, bucketSearchResult, key, value); + setAtBucket(hash, bucketSearchResult, key, value); return bucketSearchResult.getBucket() == null; } From 3c5c26d4e5e53be9a3e71025e5bff437aa052e5d Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 06:01:31 +0000 Subject: [PATCH 06/12] [Truffle] Some documentation of hash. --- .../java/org/jruby/truffle/runtime/hash/Bucket.java | 5 +++++ .../truffle/runtime/hash/BucketSearchResult.java | 13 +++++++++++++ .../java/org/jruby/truffle/runtime/hash/Entry.java | 3 +++ 3 files changed, 21 insertions(+) diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java index 812c20e3e4b..4d4683b8c6a 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java @@ -9,6 +9,11 @@ */ package org.jruby.truffle.runtime.hash; +/** + * A bucket in the Ruby hash. That is, a container for a key and a value, and a member of two lists - the chain of + * buckets for a given index, and the chain of buckets for the insertion order across the whole hash. Both of those + * chains are doubly-linked to enable O(1) deletions (after the correct bucket is found). + */ public class Bucket { private Object key; diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java index eb45441b075..4565269d4b4 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java @@ -9,6 +9,19 @@ */ package org.jruby.truffle.runtime.hash; +/** + * The result of looking for a bucket (a {@link Bucket}) in a Ruby hash. We get the last bucket in the lookup chain for + * this index, the bucket that was found, and the index that was used. There are three possible outcomes for a search. + *
    + *
  • There is nothing at that index, in which case the bucket and last bucket in the chain will be + * {@code null}
  • + *
  • There were buckets at that index, but none for our key, in which case the bucket will be null, but the last + * bucket will the last bucket in the chain at that index, presumably where we will want to insert your new + * bucket
  • + *
  • A bucket was found for our key, in which case the bucket and the last bucket in the chain will be the + * same
  • + *
+ */ public class BucketSearchResult { private final Bucket endOfLookupChain; diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java index 5f7859d0548..407865436ab 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java @@ -9,6 +9,9 @@ */ package org.jruby.truffle.runtime.hash; +/** + * A simple key-value for inserting or retrieving from a hash. + */ public class Entry { private final Object key; From 18593e6ecb6be62bf97eee7f7031d4360e1f6f14 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 07:36:13 +0000 Subject: [PATCH 07/12] [Truffle] Fix a couple of hash bugs. --- .../jruby/truffle/nodes/core/HashNodes.java | 55 +++++++++++-------- .../nodes/literal/HashLiteralNode.java | 12 ++-- .../jruby/truffle/runtime/core/RubyHash.java | 4 +- .../truffle/runtime/hash/HashOperations.java | 8 ++- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index a2c9ef81336..564e7c8cf03 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -234,7 +234,7 @@ public Object getNull(VirtualFrame frame, RubyHash hash, Object key) { @Specialization(guards = "isObjectArray") public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size && eqlNode.call(frame, store[n * 2], "eql?", null, key)) { @@ -317,7 +317,7 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje hash.checkFrozen(this); final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); for (int n = 0; n < HashOperations.SMALL_HASH_SIZE; n++) { if (n < size && eqlNode.call(frame, store[n * 2], "eql?", null, key)) { @@ -334,7 +334,7 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje extendProfile.enter(); store[size * 2] = key; store[size * 2 + 1] = value; - hash.setStoreSize(newSize); + hash.setSize(newSize); return value; } @@ -360,7 +360,7 @@ public Object setBucketArray(RubyHash hash, Object key, Object value) { notDesignedForCompilation(); if (HashOperations.verySlowSetInBuckets(hash, key, value)) { - hash.setStoreSize(hash.getStoreSize() + 1); + hash.setSize(hash.getSize() + 1); } return value; @@ -421,7 +421,7 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { hash.checkFrozen(this); final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); for (int n = 0; n < HashOperations.SMALL_HASH_SIZE * 2; n += 2) { if (n < size && eqlNode.call(frame, store[n], "eql?", null, key)) { @@ -430,7 +430,7 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { // Move the later values down System.arraycopy(store, n + 2, store, n, HashOperations.SMALL_HASH_SIZE * 2 - n - 2); - hash.setStoreSize(size - 1); + hash.setSize(size - 1); return value; } @@ -477,7 +477,7 @@ public Object delete(VirtualFrame frame, RubyHash hash, Object key) { bucket.getNextInLookup().setPreviousInLookup(bucket.getPreviousInLookup()); } - hash.setStoreSize(hash.getStoreSize() - 1); + hash.setSize(hash.getSize() - 1); return bucket.getValue(); } @@ -509,7 +509,7 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc notDesignedForCompilation(); final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); int count = 0; @@ -563,7 +563,7 @@ public boolean emptyNull(RubyHash hash) { @Specialization(guards = "!isNull") public boolean emptyObjectArray(RubyHash hash) { - return hash.getStoreSize() == 0; + return hash.getSize() == 0; } } @@ -734,7 +734,7 @@ public boolean keyNull(RubyHash hash, Object key) { public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); final Object[] store = (Object[]) hash.getStore(); for (int n = 0; n < store.length; n += 2) { @@ -783,7 +783,7 @@ public RubyArray keysObjectArray(RubyHash hash) { final Object[] store = (Object[]) hash.getStore(); - final Object[] keys = new Object[hash.getStoreSize()]; + final Object[] keys = new Object[hash.getSize()]; for (int n = 0; n < keys.length; n++) { keys[n] = store[n * 2]; @@ -796,7 +796,7 @@ public RubyArray keysObjectArray(RubyHash hash) { public RubyArray keysBucketArray(RubyHash hash) { notDesignedForCompilation(); - final Object[] keys = new Object[hash.getStoreSize()]; + final Object[] keys = new Object[hash.getSize()]; Bucket bucket = hash.getFirstInSequence(); int n = 0; @@ -828,7 +828,7 @@ public MapNode(MapNode prev) { @Specialization(guards = "isObjectArray") public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc block) { final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); final int resultSize = store.length / 2; final Object[] result = new Object[resultSize]; @@ -899,7 +899,7 @@ public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) { final Object[] store = (Object[]) hash.getStore(); final Object[] copy = Arrays.copyOf(store, HashOperations.SMALL_HASH_SIZE * 2); - return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getStoreSize(), null); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getSize(), null); } @ExplodeLoop @@ -908,10 +908,10 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R // TODO(CS): what happens with the default block here? Which side does it get merged from? final Object[] storeA = (Object[]) hash.getStore(); - final int storeASize = hash.getStoreSize(); + final int storeASize = hash.getSize(); final Object[] storeB = (Object[]) other.getStore(); - final int storeBSize = hash.getStoreSize(); + final int storeBSize = hash.getSize(); final boolean[] mergeFromA = new boolean[storeASize]; int mergeFromACount = 0; @@ -983,17 +983,24 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R @Specialization - public RubyHash mergeBucketArrayBucketArray(VirtualFrame frame, RubyHash hash, RubyHash other) { - final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[HashOperations.capacityGreaterThan(hash.getStoreSize() + hash.getStoreSize())], 0, null); + public RubyHash mergeBucketArrayBucketArray(RubyHash hash, RubyHash other) { + final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[HashOperations.capacityGreaterThan(hash.getSize() + other.getSize())], 0, null); + + int size = 0; for (Entry entry : HashOperations.verySlowToEntries(hash)) { HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue()); + size++; } for (Entry entry : HashOperations.verySlowToEntries(other)) { - HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue()); + if (HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue())) { + size++; + } } + merged.setSize(size); + return merged; } @@ -1053,7 +1060,7 @@ public int sizeNull(RubyHash hash) { @Specialization(guards = "!isNull") public int sizeObjectArray(RubyHash hash) { - return hash.getStoreSize(); + return hash.getSize(); } } @@ -1078,7 +1085,7 @@ public RubyArray valuesNull(RubyHash hash) { public RubyArray valuesObjectArray(RubyHash hash) { final Object[] store = (Object[]) hash.getStore(); - final Object[] values = new Object[hash.getStoreSize()]; + final Object[] values = new Object[hash.getSize()]; for (int n = 0; n < values.length; n++) { values[n] = store[n * 2 + 1]; @@ -1091,7 +1098,7 @@ public RubyArray valuesObjectArray(RubyHash hash) { public RubyArray valuesBucketArray(RubyHash hash) { notDesignedForCompilation(); - final Object[] values = new Object[hash.getStoreSize()]; + final Object[] values = new Object[hash.getSize()]; Bucket bucket = hash.getFirstInSequence(); int n = 0; @@ -1130,7 +1137,7 @@ public RubyArray toArrayObjectArray(RubyHash hash) { notDesignedForCompilation(); final Object[] store = (Object[]) hash.getStore(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); final Object[] pairs = new Object[size]; for (int n = 0; n < size; n++) { @@ -1144,7 +1151,7 @@ public RubyArray toArrayObjectArray(RubyHash hash) { public RubyArray toArrayBucketArray(RubyHash hash) { notDesignedForCompilation(); - final int size = hash.getStoreSize(); + final int size = hash.getSize(); final Object[] pairs = new Object[size]; int n = 0; diff --git a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java index f7cc76e7db4..e1099d4fc6c 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java @@ -90,7 +90,7 @@ public SmallHashLiteralNode(RubyContext context, SourceSection sourceSection, Ru public RubyHash executeRubyHash(VirtualFrame frame) { final Object[] storage = new Object[HashOperations.SMALL_HASH_SIZE * 2]; - int position = 0; + int end = 0; initializers: for (int n = 0; n < keyValues.length; n += 2) { Object key = keyValues[n].execute(frame); @@ -101,19 +101,19 @@ public RubyHash executeRubyHash(VirtualFrame frame) { final Object value = keyValues[n + 1].execute(frame); - for (int i = 0; i < n; i += 2) { + for (int i = 0; i < end; i += 2) { if (equalNode.call(frame, key, "eql?", null, storage[i])) { storage[i + 1] = value; continue initializers; } } - storage[position] = key; - storage[position + 1] = value; - position += 2; + storage[end] = key; + storage[end + 1] = value; + end += 2; } - return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, position / 2, null); + return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, end / 2, null); } } diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java index e4b1c9cac7a..21d06147b6c 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java @@ -74,11 +74,11 @@ public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket this.lastInSequence = lastInSequence; } - public int getStoreSize() { + public int getSize() { return storeSize; } - public void setStoreSize(int storeSize) { + public void setSize(int storeSize) { this.storeSize = storeSize; } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java index e5c474fe874..3526d5284df 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java @@ -49,7 +49,9 @@ public static RubyHash verySlowFromEntries(RubyContext context, List entr public static void dump(RubyHash hash) { final StringBuilder builder = new StringBuilder(); - builder.append("("); + builder.append("["); + builder.append(hash.getSize()); + builder.append("]("); for (Bucket bucket : (Bucket[]) hash.getStore()) { builder.append("("); @@ -109,7 +111,7 @@ public static List verySlowToEntries(RubyHash hash) { bucket = bucket.getNextInSequence(); } } else if (hash.getStore() instanceof Object[]) { - for (int n = 0; n < hash.getStoreSize(); n++) { + for (int n = 0; n < hash.getSize(); n++) { entries.add(new Entry(((Object[]) hash.getStore())[n * 2], ((Object[]) hash.getStore())[n * 2 + 1])); } } else if (hash.getStore() != null) { @@ -208,6 +210,6 @@ public static void verySlowSetEntries(RubyHash hash, List entries) { } } - hash.setStoreSize(actualSize); + hash.setSize(actualSize); } } From 35c13507781fe8d91b2603085235f99bee19ecc2 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 15:06:57 +0000 Subject: [PATCH 08/12] [Truffle] Make the bucket chain singly linked. --- .../jruby/truffle/nodes/core/HashNodes.java | 8 ++---- .../truffle/nodes/hash/FindBucketNode.java | 8 +++--- .../jruby/truffle/runtime/hash/Bucket.java | 9 ------- .../runtime/hash/BucketSearchResult.java | 25 ++++++++++--------- .../truffle/runtime/hash/HashOperations.java | 21 ++++++++-------- 5 files changed, 29 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 564e7c8cf03..19c7a956320 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -467,14 +467,10 @@ public Object delete(VirtualFrame frame, RubyHash hash, Object key) { // Remove from the lookup chain - if (bucket.getPreviousInLookup() == null) { + if (bucketSearchResult.getPreviousBucket() == null) { ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket.getNextInLookup(); } else { - bucket.getPreviousInLookup().setNextInLookup(bucket.getNextInLookup()); - } - - if (bucket.getNextInLookup() != null) { - bucket.getNextInLookup().setPreviousInLookup(bucket.getPreviousInLookup()); + bucketSearchResult.getPreviousBucket().setNextInLookup(bucket.getNextInLookup()); } hash.setSize(hash.getSize() - 1); diff --git a/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java b/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java index 2dea0ec6d6f..a3be5270b7f 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java @@ -48,18 +48,18 @@ public BucketSearchResult search(VirtualFrame frame, RubyHash hash, Object key) final int bucketIndex = (hashed & HashOperations.SIGN_BIT_MASK) % buckets.length; Bucket bucket = buckets[bucketIndex]; - Bucket endOfLookupChain = null; + Bucket previousBucket = null; while (bucket != null) { if (eqlNode.call(frame, key, "eql?", null, bucket.getKey())) { - return new BucketSearchResult(bucketIndex, bucket, bucket); + return new BucketSearchResult(bucketIndex, previousBucket, bucket); } - endOfLookupChain = bucket; + previousBucket = bucket; bucket = bucket.getNextInLookup(); } - return new BucketSearchResult(bucketIndex, endOfLookupChain, null); + return new BucketSearchResult(bucketIndex, previousBucket, null); } @Override diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java index 4d4683b8c6a..0b716082540 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java @@ -19,7 +19,6 @@ public class Bucket { private Object key; private Object value; - private Bucket previousInLookup; private Bucket nextInLookup; private Bucket previousInSequence; @@ -46,14 +45,6 @@ public void setValue(Object value) { this.value = value; } - public Bucket getPreviousInLookup() { - return previousInLookup; - } - - public void setPreviousInLookup(Bucket previousInLookup) { - this.previousInLookup = previousInLookup; - } - public Bucket getNextInLookup() { return nextInLookup; } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java index 4565269d4b4..ce575fca2d1 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java @@ -10,27 +10,28 @@ package org.jruby.truffle.runtime.hash; /** - * The result of looking for a bucket (a {@link Bucket}) in a Ruby hash. We get the last bucket in the lookup chain for - * this index, the bucket that was found, and the index that was used. There are three possible outcomes for a search. + * The result of looking for a bucket (a {@link Bucket}) in a Ruby hash. We get the previous bucket in the lookup chain + * for this index until the bucket was found, the bucket that was found, and the index that was used. There are three + * possible outcomes for a search. *
    *
  • There is nothing at that index, in which case the bucket and last bucket in the chain will be * {@code null}
  • - *
  • There were buckets at that index, but none for our key, in which case the bucket will be null, but the last - * bucket will the last bucket in the chain at that index, presumably where we will want to insert your new - * bucket
  • - *
  • A bucket was found for our key, in which case the bucket and the last bucket in the chain will be the - * same
  • + *
  • There were buckets at that index, but none for our key, in which case the bucket will be null, but the + * previous bucket will be the last bucket in the chain at that index, presumably where we will want to insert our + * new bucket
  • + *
  • A bucket was found for our key, in which case the bucket will be the one correspond to the key, and the + * previous bucket will be the one in the bucket chain before that one
  • *
*/ public class BucketSearchResult { - private final Bucket endOfLookupChain; + private final Bucket previousBucket; private final Bucket bucket; private final int index; - public BucketSearchResult(int index, Bucket endOfLookupChain, Bucket bucket) { + public BucketSearchResult(int index, Bucket previousBucket, Bucket bucket) { this.index = index; - this.endOfLookupChain = endOfLookupChain; + this.previousBucket = previousBucket; this.bucket = bucket; } @@ -38,8 +39,8 @@ public int getIndex() { return index; } - public Bucket getEndOfLookupChain() { - return endOfLookupChain; + public Bucket getPreviousBucket() { + return previousBucket; } public Bucket getBucket() { diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java index 3526d5284df..48d027b0e27 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java @@ -139,26 +139,32 @@ public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; Bucket bucket = buckets[bucketIndex]; - Bucket endOfLookupChain = null; + Bucket previousBucket = null; while (bucket != null) { // TODO: cast if ((boolean) DebugOperations.send(hash.getContext(), key, "eql?", null, bucket.getKey())) { - return new BucketSearchResult(bucketIndex, bucket, bucket); + return new BucketSearchResult(bucketIndex, previousBucket, bucket); } - endOfLookupChain = bucket; + previousBucket = bucket; bucket = bucket.getNextInLookup(); } - return new BucketSearchResult(bucketIndex, endOfLookupChain, null); + return new BucketSearchResult(bucketIndex, previousBucket, null); } public static void setAtBucket(RubyHash hash, BucketSearchResult bucketSearchResult, Object key, Object value) { if (bucketSearchResult.getBucket() == null) { final Bucket bucket = new Bucket(key, value); + if (bucketSearchResult.getPreviousBucket() == null) { + ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket; + } else { + bucketSearchResult.getPreviousBucket().setNextInLookup(bucket); + } + if (hash.getFirstInSequence() == null) { hash.setFirstInSequence(bucket); hash.setLastInSequence(bucket); @@ -167,13 +173,6 @@ public static void setAtBucket(RubyHash hash, BucketSearchResult bucketSearchRes bucket.setPreviousInSequence(hash.getLastInSequence()); hash.setLastInSequence(bucket); } - - if (bucketSearchResult.getEndOfLookupChain() == null) { - ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket; - } else { - bucketSearchResult.getEndOfLookupChain().setNextInLookup(bucket); - bucket.setPreviousInLookup(bucketSearchResult.getEndOfLookupChain()); - } } else { final Bucket bucket = bucketSearchResult.getBucket(); From 96c8da62e6344b317bd99ead43626e7060e75440 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 15:21:51 +0000 Subject: [PATCH 09/12] [Truffle] Rename buckets entries. --- .../org/jruby/truffle/nodes/RubyNode.java | 7 +- .../org/jruby/truffle/nodes/RubyTypes.java | 5 +- .../jruby/truffle/nodes/core/HashGuards.java | 6 +- .../jruby/truffle/nodes/core/HashNodes.java | 120 +++++++++--------- .../jruby/truffle/nodes/core/KernelNodes.java | 6 +- .../jruby/truffle/nodes/core/SystemNode.java | 6 +- ...FindBucketNode.java => FindEntryNode.java} | 30 ++--- .../nodes/literal/HashLiteralNode.java | 6 +- .../methods/arguments/CheckArityNode.java | 8 +- .../arguments/ReadKeywordArgumentNode.java | 8 +- .../ReadKeywordRestArgumentNode.java | 10 +- .../truffle/runtime/core/CoreLibrary.java | 6 +- .../jruby/truffle/runtime/core/RubyHash.java | 28 ++-- .../jruby/truffle/runtime/hash/Bucket.java | 72 ----------- .../runtime/hash/BucketSearchResult.java | 50 -------- .../org/jruby/truffle/runtime/hash/Entry.java | 48 ++++++- .../truffle/runtime/hash/HashOperations.java | 118 ++++++++--------- .../runtime/hash/HashSearchResult.java | 50 ++++++++ .../jruby/truffle/runtime/hash/KeyValue.java | 33 +++++ 19 files changed, 307 insertions(+), 310 deletions(-) rename core/src/main/java/org/jruby/truffle/nodes/hash/{FindBucketNode.java => FindEntryNode.java} (63%) delete mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java delete mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/HashSearchResult.java create mode 100644 core/src/main/java/org/jruby/truffle/runtime/hash/KeyValue.java diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java index 1bafcfa6c0a..fe38857a8fc 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java @@ -10,7 +10,6 @@ package org.jruby.truffle.nodes; import com.oracle.truffle.api.CompilerAsserts; -import com.oracle.truffle.api.dsl.ImportGuards; import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.api.dsl.TypeSystemReference; import com.oracle.truffle.api.frame.VirtualFrame; @@ -24,7 +23,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyRange; import org.jruby.truffle.runtime.core.RubyBasicObject; -import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.HashSearchResult; import org.jruby.truffle.runtime.rubinius.RubiniusByteArray; import org.jruby.truffle.runtime.rubinius.RubiniusChannel; @@ -192,8 +191,8 @@ public RubyEncodingConverter executeRubyEncodingConverter(VirtualFrame frame) th return RubyTypesGen.RUBYTYPES.expectRubyEncodingConverter(execute(frame)); } - public BucketSearchResult executeBucketSearchResult(VirtualFrame frame) throws UnexpectedResultException { - return RubyTypesGen.RUBYTYPES.expectBucketSearchResult(execute(frame)); + public HashSearchResult executeBucketSearchResult(VirtualFrame frame) throws UnexpectedResultException { + return RubyTypesGen.RUBYTYPES.expectHashSearchResult(execute(frame)); } public Dispatch.DispatchAction executeDispatchAction(VirtualFrame frame) { diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java index 5faf3c5a4f7..89e957ddc93 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java @@ -9,7 +9,6 @@ */ package org.jruby.truffle.nodes; -import com.oracle.truffle.api.dsl.ImplicitCast; import com.oracle.truffle.api.dsl.TypeSystem; import org.jruby.truffle.nodes.dispatch.Dispatch; import org.jruby.truffle.runtime.UndefinedPlaceholder; @@ -18,7 +17,7 @@ import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyRange; import org.jruby.truffle.runtime.core.RubyBasicObject; -import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.HashSearchResult; import org.jruby.truffle.runtime.rubinius.RubiniusByteArray; import org.jruby.truffle.runtime.rubinius.RubiniusChannel; import org.jruby.truffle.runtime.LexicalScope; @@ -62,7 +61,7 @@ RubiniusByteArray.class, // RubyEncodingConverter.class, // RubyBasicObject.class, // - BucketSearchResult.class, // + HashSearchResult.class, // Object[].class}) public class RubyTypes { diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java index d48f47a0efd..4479864f677 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java @@ -10,7 +10,7 @@ package org.jruby.truffle.nodes.core; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Bucket; +import org.jruby.truffle.runtime.hash.Entry; public class HashGuards { @@ -20,11 +20,11 @@ public static boolean isNull(RubyHash hash) { public static boolean isObjectArray(RubyHash hash) { // Arrays are covariant in Java! - return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof Bucket[]); + return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof Entry[]); } public static boolean isBucketArray(RubyHash hash) { - return hash.getStore() instanceof Bucket[]; + return hash.getStore() instanceof Entry[]; } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 19c7a956320..88dfa224c37 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -19,15 +19,15 @@ import org.jruby.truffle.nodes.RubyRootNode; import org.jruby.truffle.nodes.dispatch.DispatchHeadNode; import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; -import org.jruby.truffle.nodes.hash.FindBucketNode; +import org.jruby.truffle.nodes.hash.FindEntryNode; import org.jruby.truffle.nodes.yield.YieldDispatchHeadNode; import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.*; import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Bucket; -import org.jruby.truffle.runtime.hash.BucketSearchResult; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashSearchResult; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import java.util.ArrayList; @@ -61,8 +61,8 @@ public boolean equalNull(RubyHash a, RubyHash b) { public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { notDesignedForCompilation(); - final List aEntries = HashOperations.verySlowToEntries(a); - final List bEntries = HashOperations.verySlowToEntries(a); + final List aEntries = HashOperations.verySlowToKeyValues(a); + final List bEntries = HashOperations.verySlowToKeyValues(a); if (aEntries.size() != bEntries.size()) { return false; @@ -72,14 +72,14 @@ public boolean equal(VirtualFrame frame, RubyHash a, RubyHash b) { final boolean[] bUsed = new boolean[bEntries.size()]; - for (Entry aEntry : aEntries) { + for (KeyValue aKeyValue : aEntries) { boolean found = false; for (int n = 0; n < bEntries.size(); n++) { if (!bUsed[n]) { // TODO: cast - if ((boolean) DebugOperations.send(getContext(), aEntry.getKey(), "eql?", null, bEntries.get(n).getKey())) { + if ((boolean) DebugOperations.send(getContext(), aKeyValue.getKey(), "eql?", null, bEntries.get(n).getKey())) { bUsed[n] = true; found = true; break; @@ -181,10 +181,10 @@ public RubyHash construct(Object[] args) { } else { keyValues.enter(); - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (int n = 0; n < args.length; n += 2) { - entries.add(new Entry(args[n], args[n + 1])); + entries.add(new KeyValue(args[n], args[n + 1])); } return HashOperations.verySlowFromEntries(getContext(), entries); @@ -198,7 +198,7 @@ public abstract static class GetIndexNode extends HashCoreMethodNode { @Child protected PredicateDispatchHeadNode eqlNode; @Child protected YieldDispatchHeadNode yield; - @Child protected FindBucketNode findBucketNode; + @Child protected FindEntryNode findEntryNode; private final BranchProfile notInHashProfile = new BranchProfile(); private final BranchProfile useDefaultProfile = new BranchProfile(); @@ -207,14 +207,14 @@ public GetIndexNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); eqlNode = new PredicateDispatchHeadNode(context); yield = new YieldDispatchHeadNode(context); - findBucketNode = new FindBucketNode(context, sourceSection); + findEntryNode = new FindEntryNode(context, sourceSection); } public GetIndexNode(GetIndexNode prev) { super(prev); eqlNode = prev.eqlNode; yield = prev.yield; - findBucketNode = prev.findBucketNode; + findEntryNode = prev.findEntryNode; } @Specialization(guards = "isNull") @@ -261,10 +261,10 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = findBucketNode.search(frame, hash, key); + final HashSearchResult hashSearchResult = findEntryNode.search(frame, hash, key); - if (bucketSearchResult.getBucket() != null) { - return bucketSearchResult.getBucket().getValue(); + if (hashSearchResult.getEntry() != null) { + return hashSearchResult.getEntry().getValue(); } notInHashProfile.enter(); @@ -342,12 +342,12 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje // TODO(CS): need to watch for that transfer until we make the following fast path - final List entries = HashOperations.verySlowToEntries(hash); + final List entries = HashOperations.verySlowToKeyValues(hash); - hash.setStore(new Bucket[HashOperations.capacityGreaterThan(newSize)], newSize, null, null); + hash.setStore(new Entry[HashOperations.capacityGreaterThan(newSize)], newSize, null, null); - for (Entry entry : entries) { - HashOperations.verySlowSetInBuckets(hash, entry.getKey(), entry.getValue()); + for (KeyValue keyValue : entries) { + HashOperations.verySlowSetInBuckets(hash, keyValue.getKey(), keyValue.getValue()); } HashOperations.verySlowSetInBuckets(hash, key, value); @@ -396,18 +396,18 @@ public RubyHash empty(RubyHash hash) { public abstract static class DeleteNode extends HashCoreMethodNode { @Child protected PredicateDispatchHeadNode eqlNode; - @Child protected FindBucketNode findBucketNode; + @Child protected FindEntryNode findEntryNode; public DeleteNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); eqlNode = new PredicateDispatchHeadNode(context); - findBucketNode = new FindBucketNode(context, sourceSection); + findEntryNode = new FindEntryNode(context, sourceSection); } public DeleteNode(DeleteNode prev) { super(prev); eqlNode = prev.eqlNode; - findBucketNode = prev.findBucketNode; + findEntryNode = prev.findEntryNode; } @Specialization(guards = "isNull") @@ -443,39 +443,39 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public Object delete(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - final BucketSearchResult bucketSearchResult = findBucketNode.search(frame, hash, key); + final HashSearchResult hashSearchResult = findEntryNode.search(frame, hash, key); - if (bucketSearchResult.getBucket() == null) { + if (hashSearchResult.getEntry() == null) { return getContext().getCoreLibrary().getNilObject(); } - final Bucket bucket = bucketSearchResult.getBucket(); + final Entry entry = hashSearchResult.getEntry(); // Remove from the sequence chain - if (bucket.getPreviousInSequence() == null) { - hash.setFirstInSequence(bucket.getNextInSequence()); + if (entry.getPreviousInSequence() == null) { + hash.setFirstInSequence(entry.getNextInSequence()); } else { - bucket.getPreviousInSequence().setNextInSequence(bucket.getNextInSequence()); + entry.getPreviousInSequence().setNextInSequence(entry.getNextInSequence()); } - if (bucket.getNextInSequence() == null) { - hash.setLastInSequence(bucket.getPreviousInSequence()); + if (entry.getNextInSequence() == null) { + hash.setLastInSequence(entry.getPreviousInSequence()); } else { - bucket.getNextInSequence().setPreviousInSequence(bucket.getPreviousInSequence()); + entry.getNextInSequence().setPreviousInSequence(entry.getPreviousInSequence()); } // Remove from the lookup chain - if (bucketSearchResult.getPreviousBucket() == null) { - ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket.getNextInLookup(); + if (hashSearchResult.getPreviousEntry() == null) { + ((Entry[]) hash.getStore())[hashSearchResult.getIndex()] = entry.getNextInLookup(); } else { - bucketSearchResult.getPreviousBucket().setNextInLookup(bucket.getNextInLookup()); + hashSearchResult.getPreviousEntry().setNextInLookup(entry.getNextInLookup()); } hash.setSize(hash.getSize() - 1); - return bucket.getValue(); + return entry.getValue(); } } @@ -532,8 +532,8 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc public RubyHash eachBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue())); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), keyValue.getKey(), keyValue.getValue())); } return hash; @@ -650,7 +650,7 @@ public RubyHash dupBucketArray(RubyHash self, RubyHash from) { return self; } - HashOperations.verySlowSetEntries(self, HashOperations.verySlowToEntries(from)); + HashOperations.verySlowSetKeyValues(self, HashOperations.verySlowToKeyValues(from)); return self; } @@ -687,16 +687,16 @@ public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) { builder.append("{"); - for (Entry entry : HashOperations.verySlowToEntries(hash)) { + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { if (builder.length() > 1) { builder.append(", "); } // TODO(CS): to string - builder.append(inspect.call(frame, entry.getKey(), "inspect", null)); + builder.append(inspect.call(frame, keyValue.getKey(), "inspect", null)); builder.append("=>"); - builder.append(inspect.call(frame, entry.getValue(), "inspect", null)); + builder.append(inspect.call(frame, keyValue.getValue(), "inspect", null)); } builder.append("}"); @@ -746,8 +746,8 @@ public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { public boolean keyBucketArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - if (eqlNode.call(frame, entry.getKey(), "eql?", null, key)) { + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + if (eqlNode.call(frame, keyValue.getKey(), "eql?", null, key)) { return true; } } @@ -794,13 +794,13 @@ public RubyArray keysBucketArray(RubyHash hash) { final Object[] keys = new Object[hash.getSize()]; - Bucket bucket = hash.getFirstInSequence(); + Entry entry = hash.getFirstInSequence(); int n = 0; - while (bucket != null) { - keys[n] = bucket.getKey(); + while (entry != null) { + keys[n] = entry.getKey(); n++; - bucket = bucket.getNextInSequence(); + entry = entry.getNextInSequence(); } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length); @@ -858,8 +858,8 @@ public RubyArray mapBucketArray(VirtualFrame frame, RubyHash hash, RubyProc bloc final RubyArray array = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - array.slowPush(yield(frame, block, entry.getKey(), entry.getValue())); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + array.slowPush(yield(frame, block, keyValue.getKey(), keyValue.getValue())); } return array; @@ -980,17 +980,17 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R @Specialization public RubyHash mergeBucketArrayBucketArray(RubyHash hash, RubyHash other) { - final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Bucket[HashOperations.capacityGreaterThan(hash.getSize() + other.getSize())], 0, null); + final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Entry[HashOperations.capacityGreaterThan(hash.getSize() + other.getSize())], 0, null); int size = 0; - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue()); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + HashOperations.verySlowSetInBuckets(merged, keyValue.getKey(), keyValue.getValue()); size++; } - for (Entry entry : HashOperations.verySlowToEntries(other)) { - if (HashOperations.verySlowSetInBuckets(merged, entry.getKey(), entry.getValue())) { + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(other)) { + if (HashOperations.verySlowSetInBuckets(merged, keyValue.getKey(), keyValue.getValue())) { size++; } } @@ -1096,13 +1096,13 @@ public RubyArray valuesBucketArray(RubyHash hash) { final Object[] values = new Object[hash.getSize()]; - Bucket bucket = hash.getFirstInSequence(); + Entry entry = hash.getFirstInSequence(); int n = 0; - while (bucket != null) { - values[n] = bucket.getValue(); + while (entry != null) { + values[n] = entry.getValue(); n++; - bucket = bucket.getNextInSequence(); + entry = entry.getNextInSequence(); } return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length); @@ -1152,8 +1152,8 @@ public RubyArray toArrayBucketArray(RubyHash hash) { int n = 0; - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getValue(), entry.getValue()); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), keyValue.getValue(), keyValue.getValue()); n++; } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java index 84c82e8d77e..0aca28a450b 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java @@ -37,7 +37,7 @@ import org.jruby.truffle.runtime.core.*; import org.jruby.truffle.runtime.core.RubyArray; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.methods.RubyMethod; import org.jruby.util.cli.Options; @@ -598,8 +598,8 @@ private static void exec(RubyContext context, String[] commandLine) { final RubyHash env = context.getCoreLibrary().getENV(); - for (Entry entry : HashOperations.verySlowToEntries(env)) { - builder.environment().put(entry.getKey().toString(), entry.getValue().toString()); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(env)) { + builder.environment().put(keyValue.getKey().toString(), keyValue.getValue().toString()); } Process process; diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java index 9d58bc1a72f..9692700f510 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/SystemNode.java @@ -18,7 +18,7 @@ import org.jruby.truffle.nodes.*; import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; /** @@ -45,8 +45,8 @@ public Object execute(VirtualFrame frame) { final List envp = new ArrayList<>(); // TODO(CS): cast - for (Entry entry : HashOperations.verySlowToEntries(env)) { - envp.add(entry.getKey().toString() + "=" + entry.getValue().toString()); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(env)) { + envp.add(keyValue.getKey().toString() + "=" + keyValue.getValue().toString()); } final String command = child.execute(frame).toString(); diff --git a/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java b/core/src/main/java/org/jruby/truffle/nodes/hash/FindEntryNode.java similarity index 63% rename from core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java rename to core/src/main/java/org/jruby/truffle/nodes/hash/FindEntryNode.java index a3be5270b7f..646b0089194 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/hash/FindBucketNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/hash/FindEntryNode.java @@ -16,22 +16,22 @@ import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Bucket; -import org.jruby.truffle.runtime.hash.BucketSearchResult; +import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.HashSearchResult; import org.jruby.truffle.runtime.hash.HashOperations; -public class FindBucketNode extends RubyNode { +public class FindEntryNode extends RubyNode { @Child DispatchHeadNode hashNode; @Child PredicateDispatchHeadNode eqlNode; - public FindBucketNode(RubyContext context, SourceSection sourceSection) { + public FindEntryNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); hashNode = new DispatchHeadNode(context); eqlNode = new PredicateDispatchHeadNode(context); } - public BucketSearchResult search(VirtualFrame frame, RubyHash hash, Object key) { + public HashSearchResult search(VirtualFrame frame, RubyHash hash, Object key) { final Object hashValue = hashNode.call(frame, key, "hash", null); final int hashed; @@ -44,22 +44,22 @@ public BucketSearchResult search(VirtualFrame frame, RubyHash hash, Object key) throw new UnsupportedOperationException(); } - final Bucket[] buckets = (Bucket[]) hash.getStore(); - final int bucketIndex = (hashed & HashOperations.SIGN_BIT_MASK) % buckets.length; - Bucket bucket = buckets[bucketIndex]; + final Entry[] entries = (Entry[]) hash.getStore(); + final int index = (hashed & HashOperations.SIGN_BIT_MASK) % entries.length; + Entry entry = entries[index]; - Bucket previousBucket = null; + Entry previousEntry = null; - while (bucket != null) { - if (eqlNode.call(frame, key, "eql?", null, bucket.getKey())) { - return new BucketSearchResult(bucketIndex, previousBucket, bucket); + while (entry != null) { + if (eqlNode.call(frame, key, "eql?", null, entry.getKey())) { + return new HashSearchResult(index, previousEntry, entry); } - previousBucket = bucket; - bucket = bucket.getNextInLookup(); + previousEntry = entry; + entry = entry.getNextInLookup(); } - return new BucketSearchResult(bucketIndex, previousBucket, null); + return new HashSearchResult(index, previousEntry, null); } @Override diff --git a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java index e1099d4fc6c..942e93f25df 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/literal/HashLiteralNode.java @@ -18,7 +18,7 @@ import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.core.RubyHash; import org.jruby.truffle.runtime.core.RubyString; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import java.util.*; @@ -128,12 +128,12 @@ public GenericHashLiteralNode(RubyContext context, SourceSection sourceSection, public RubyHash executeRubyHash(VirtualFrame frame) { notDesignedForCompilation(); - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (int n = 0; n < keyValues.length; n += 2) { final Object key = keyValues[n].execute(frame); final Object value = keyValues[n + 1].execute(frame); - entries.add(new Entry(key, value)); + entries.add(new KeyValue(key, value)); } return HashOperations.verySlowFromEntries(getContext(), entries); diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java index 2dccef1cf05..cfb65eef5f7 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/CheckArityNode.java @@ -16,7 +16,7 @@ import org.jruby.truffle.runtime.*; import org.jruby.truffle.runtime.control.RaiseException; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.methods.*; @@ -50,10 +50,10 @@ public void executeVoid(VirtualFrame frame) { } if (!keywordsRest && arity.hasKeywords() && getKeywordsHash(frame) != null) { - for (Entry entry : HashOperations.verySlowToEntries(getKeywordsHash(frame))) { + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(getKeywordsHash(frame))) { for (String keyword : keywords) { - if (!keyword.toString().equals(entry.getKey().toString())) { - throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + entry.getKey().toString(), this)); + if (!keyword.toString().equals(keyValue.getKey().toString())) { + throw new RaiseException(getContext().getCoreLibrary().argumentError("unknown keyword: " + keyValue.getKey().toString(), this)); } } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java index b1a211e15ea..cfacca82810 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordArgumentNode.java @@ -15,7 +15,7 @@ import org.jruby.truffle.runtime.RubyArguments; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; public class ReadKeywordArgumentNode extends RubyNode { @@ -43,9 +43,9 @@ public Object execute(VirtualFrame frame) { Object value = null; - for (Entry entry : HashOperations.verySlowToEntries(hash)) { - if (entry.getKey().toString().equals(name)) { - value = entry.getValue(); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { + if (keyValue.getKey().toString().equals(name)) { + value = keyValue.getValue(); break; } } diff --git a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java index df944dc432d..7ef55acda24 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/methods/arguments/ReadKeywordRestArgumentNode.java @@ -15,7 +15,7 @@ import org.jruby.truffle.runtime.RubyArguments; import org.jruby.truffle.runtime.RubyContext; import org.jruby.truffle.runtime.core.RubyHash; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import java.util.*; @@ -41,16 +41,16 @@ public Object execute(VirtualFrame frame) { return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0, null); } - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); - outer: for (Entry entry : HashOperations.verySlowToEntries(hash)) { + outer: for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { for (String excludedKeyword : excludedKeywords) { - if (excludedKeyword.toString().equals(entry.getKey().toString())) { + if (excludedKeyword.toString().equals(keyValue.getKey().toString())) { continue outer; } } - entries.add(new Entry(entry.getKey(), entry.getValue())); + entries.add(new KeyValue(keyValue.getKey(), keyValue.getValue())); } return HashOperations.verySlowFromEntries(getContext(), entries); diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java index de2bf62539b..5a2d29e4f35 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java @@ -21,7 +21,7 @@ import org.jruby.truffle.nodes.core.ArrayNodes; import org.jruby.truffle.runtime.RubyCallStack; import org.jruby.truffle.runtime.RubyContext; -import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.rubinius.RubiniusLibrary; import org.jruby.truffle.translator.TranslatorDriver; @@ -737,10 +737,10 @@ public RubyHash getENV() { public RubyEncoding getDefaultEncoding() { return RubyEncoding.getEncoding(context, "US-ASCII"); } private RubyHash getSystemEnv() { - final List entries = new ArrayList<>(); + final List entries = new ArrayList<>(); for (Map.Entry variable : System.getenv().entrySet()) { - entries.add(new Entry(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); + entries.add(new KeyValue(context.makeString(variable.getKey()), context.makeString(variable.getValue()))); } return HashOperations.verySlowFromEntries(context, entries); diff --git a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java index 21d06147b6c..786032d6113 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java +++ b/core/src/main/java/org/jruby/truffle/runtime/core/RubyHash.java @@ -11,8 +11,8 @@ import org.jruby.truffle.nodes.RubyNode; import org.jruby.truffle.runtime.RubyContext; -import org.jruby.truffle.runtime.hash.Bucket; import org.jruby.truffle.runtime.hash.Entry; +import org.jruby.truffle.runtime.hash.KeyValue; import org.jruby.truffle.runtime.hash.HashOperations; import org.jruby.truffle.runtime.subsystems.ObjectSpaceManager; @@ -35,10 +35,10 @@ public RubyBasicObject newInstance(RubyNode currentNode) { private Object defaultValue; private Object store; private int storeSize; - private Bucket firstInSequence; - private Bucket lastInSequence; + private Entry firstInSequence; + private Entry lastInSequence; - public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize, Bucket firstInSequence) { + public RubyHash(RubyClass rubyClass, RubyProc defaultBlock, Object defaultValue, Object store, int storeSize, Entry firstInSequence) { super(rubyClass); this.defaultBlock = defaultBlock; this.defaultValue = defaultValue; @@ -67,7 +67,7 @@ public Object getStore() { return store; } - public void setStore(Object store, int storeSize, Bucket firstInSequence, Bucket lastInSequence) { + public void setStore(Object store, int storeSize, Entry firstInSequence, Entry lastInSequence) { this.store = store; this.storeSize = storeSize; this.firstInSequence = firstInSequence; @@ -82,31 +82,31 @@ public void setSize(int storeSize) { this.storeSize = storeSize; } - public Bucket getFirstInSequence() { + public Entry getFirstInSequence() { return firstInSequence; } - public void setFirstInSequence(Bucket firstInSequence) { + public void setFirstInSequence(Entry firstInSequence) { this.firstInSequence = firstInSequence; } - public Bucket getLastInSequence() { + public Entry getLastInSequence() { return lastInSequence; } - public void setLastInSequence(Bucket lastInSequence) { + public void setLastInSequence(Entry lastInSequence) { this.lastInSequence = lastInSequence; } @Override public void visitObjectGraphChildren(ObjectSpaceManager.ObjectGraphVisitor visitor) { - for (Entry entry : HashOperations.verySlowToEntries(this)) { - if (entry.getKey() instanceof RubyBasicObject) { - ((RubyBasicObject) entry.getKey()).visitObjectGraph(visitor); + for (KeyValue keyValue : HashOperations.verySlowToKeyValues(this)) { + if (keyValue.getKey() instanceof RubyBasicObject) { + ((RubyBasicObject) keyValue.getKey()).visitObjectGraph(visitor); } - if (entry.getValue() instanceof RubyBasicObject) { - ((RubyBasicObject) entry.getValue()).visitObjectGraph(visitor); + if (keyValue.getValue() instanceof RubyBasicObject) { + ((RubyBasicObject) keyValue.getValue()).visitObjectGraph(visitor); } } } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java deleted file mode 100644 index 0b716082540..00000000000 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/Bucket.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 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.hash; - -/** - * A bucket in the Ruby hash. That is, a container for a key and a value, and a member of two lists - the chain of - * buckets for a given index, and the chain of buckets for the insertion order across the whole hash. Both of those - * chains are doubly-linked to enable O(1) deletions (after the correct bucket is found). - */ -public class Bucket { - - private Object key; - private Object value; - - private Bucket nextInLookup; - - private Bucket previousInSequence; - private Bucket nextInSequence; - - public Bucket(Object key, Object value) { - this.key = key; - this.value = value; - } - - public Object getKey() { - return key; - } - - public void setKey(Object key) { - this.key = key; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - - public Bucket getNextInLookup() { - return nextInLookup; - } - - public void setNextInLookup(Bucket nextInLookup) { - this.nextInLookup = nextInLookup; - } - - public Bucket getPreviousInSequence() { - return previousInSequence; - } - - public void setPreviousInSequence(Bucket previousInSequence) { - this.previousInSequence = previousInSequence; - } - - public Bucket getNextInSequence() { - return nextInSequence; - } - - public void setNextInSequence(Bucket nextInSequence) { - this.nextInSequence = nextInSequence; - } - -} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java b/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java deleted file mode 100644 index ce575fca2d1..00000000000 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/BucketSearchResult.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 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.hash; - -/** - * The result of looking for a bucket (a {@link Bucket}) in a Ruby hash. We get the previous bucket in the lookup chain - * for this index until the bucket was found, the bucket that was found, and the index that was used. There are three - * possible outcomes for a search. - *
    - *
  • There is nothing at that index, in which case the bucket and last bucket in the chain will be - * {@code null}
  • - *
  • There were buckets at that index, but none for our key, in which case the bucket will be null, but the - * previous bucket will be the last bucket in the chain at that index, presumably where we will want to insert our - * new bucket
  • - *
  • A bucket was found for our key, in which case the bucket will be the one correspond to the key, and the - * previous bucket will be the one in the bucket chain before that one
  • - *
- */ -public class BucketSearchResult { - - private final Bucket previousBucket; - private final Bucket bucket; - private final int index; - - public BucketSearchResult(int index, Bucket previousBucket, Bucket bucket) { - this.index = index; - this.previousBucket = previousBucket; - this.bucket = bucket; - } - - public int getIndex() { - return index; - } - - public Bucket getPreviousBucket() { - return previousBucket; - } - - public Bucket getBucket() { - return bucket; - } - -} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java index 407865436ab..2511c03fc48 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/Entry.java @@ -10,24 +10,62 @@ package org.jruby.truffle.runtime.hash; /** - * A simple key-value for inserting or retrieving from a hash. + * An entry in the Ruby hash. That is, a container for a key and a value, and a member of two lists - the chain of + * buckets for a given index, and the chain of entries for the insertion order across the whole hash. */ public class Entry { - private final Object key; - private final Object value; + private Object key; + private Object value; + + private Entry nextInLookup; + + private Entry previousInSequence; + private Entry nextInSequence; public Entry(Object key, Object value) { this.key = key; this.value = value; } + public Object getKey() { + return key; + } + + public void setKey(Object key) { + this.key = key; + } + public Object getValue() { return value; } - public Object getKey() { - return key; + public void setValue(Object value) { + this.value = value; + } + + public Entry getNextInLookup() { + return nextInLookup; + } + + public void setNextInLookup(Entry nextInLookup) { + this.nextInLookup = nextInLookup; + } + + public Entry getPreviousInSequence() { + return previousInSequence; + } + + public void setPreviousInSequence(Entry previousInSequence) { + this.previousInSequence = previousInSequence; + } + + public Entry getNextInSequence() { + return nextInSequence; + } + + public void setNextInSequence(Entry nextInSequence) { + this.nextInSequence = nextInSequence; } } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java index 48d027b0e27..aaafa459d3d 100644 --- a/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashOperations.java @@ -38,11 +38,11 @@ public static int capacityGreaterThan(int size) { } @CompilerDirectives.SlowPath - public static RubyHash verySlowFromEntries(RubyContext context, List entries) { + public static RubyHash verySlowFromEntries(RubyContext context, List entries) { RubyNode.notDesignedForCompilation(); final RubyHash hash = new RubyHash(context.getCoreLibrary().getHashClass(), null, null, null, 0, null); - verySlowSetEntries(hash, entries); + verySlowSetKeyValues(hash, entries); return hash; } @@ -53,16 +53,16 @@ public static void dump(RubyHash hash) { builder.append(hash.getSize()); builder.append("]("); - for (Bucket bucket : (Bucket[]) hash.getStore()) { + for (Entry entry : (Entry[]) hash.getStore()) { builder.append("("); - while (bucket != null) { + while (entry != null) { builder.append("["); - builder.append(bucket.getKey()); + builder.append(entry.getKey()); builder.append(","); - builder.append(bucket.getValue()); + builder.append(entry.getValue()); builder.append("]"); - bucket = bucket.getNextInLookup(); + entry = entry.getNextInLookup(); } builder.append(")"); @@ -70,28 +70,28 @@ public static void dump(RubyHash hash) { builder.append(")~>("); - Bucket bucket = hash.getFirstInSequence(); + Entry entry = hash.getFirstInSequence(); - while (bucket != null) { + while (entry != null) { builder.append("["); - builder.append(bucket.getKey()); + builder.append(entry.getKey()); builder.append(","); - builder.append(bucket.getValue()); + builder.append(entry.getValue()); builder.append("]"); - bucket = bucket.getNextInSequence(); + entry = entry.getNextInSequence(); } builder.append(")<~("); - bucket = hash.getLastInSequence(); + entry = hash.getLastInSequence(); - while (bucket != null) { + while (entry != null) { builder.append("["); - builder.append(bucket.getKey()); + builder.append(entry.getKey()); builder.append(","); - builder.append(bucket.getValue()); + builder.append(entry.getValue()); builder.append("]"); - bucket = bucket.getPreviousInSequence(); + entry = entry.getPreviousInSequence(); } builder.append(")"); @@ -100,29 +100,29 @@ public static void dump(RubyHash hash) { } @CompilerDirectives.SlowPath - public static List verySlowToEntries(RubyHash hash) { - final List entries = new ArrayList<>(); + public static List verySlowToKeyValues(RubyHash hash) { + final List keyValues = new ArrayList<>(); - if (hash.getStore() instanceof Bucket[]) { - Bucket bucket = hash.getFirstInSequence(); + if (hash.getStore() instanceof Entry[]) { + Entry entry = hash.getFirstInSequence(); - while (bucket != null) { - entries.add(new Entry(bucket.getKey(), bucket.getValue())); - bucket = bucket.getNextInSequence(); + while (entry != null) { + keyValues.add(new KeyValue(entry.getKey(), entry.getValue())); + entry = entry.getNextInSequence(); } } else if (hash.getStore() instanceof Object[]) { for (int n = 0; n < hash.getSize(); n++) { - entries.add(new Entry(((Object[]) hash.getStore())[n * 2], ((Object[]) hash.getStore())[n * 2 + 1])); + keyValues.add(new KeyValue(((Object[]) hash.getStore())[n * 2], ((Object[]) hash.getStore())[n * 2 + 1])); } } else if (hash.getStore() != null) { throw new UnsupportedOperationException(); } - return entries; + return keyValues; } @CompilerDirectives.SlowPath - public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { + public static HashSearchResult verySlowFindBucket(RubyHash hash, Object key) { final Object hashValue = DebugOperations.send(hash.getContext(), key, "hash", null); final int hashed; @@ -135,53 +135,53 @@ public static BucketSearchResult verySlowFindBucket(RubyHash hash, Object key) { throw new UnsupportedOperationException(); } - final Bucket[] buckets = (Bucket[]) hash.getStore(); - final int bucketIndex = (hashed & SIGN_BIT_MASK) % buckets.length; - Bucket bucket = buckets[bucketIndex]; + final Entry[] entries = (Entry[]) hash.getStore(); + final int bucketIndex = (hashed & SIGN_BIT_MASK) % entries.length; + Entry entry = entries[bucketIndex]; - Bucket previousBucket = null; + Entry previousEntry = null; - while (bucket != null) { + while (entry != null) { // TODO: cast - if ((boolean) DebugOperations.send(hash.getContext(), key, "eql?", null, bucket.getKey())) { - return new BucketSearchResult(bucketIndex, previousBucket, bucket); + if ((boolean) DebugOperations.send(hash.getContext(), key, "eql?", null, entry.getKey())) { + return new HashSearchResult(bucketIndex, previousEntry, entry); } - previousBucket = bucket; - bucket = bucket.getNextInLookup(); + previousEntry = entry; + entry = entry.getNextInLookup(); } - return new BucketSearchResult(bucketIndex, previousBucket, null); + return new HashSearchResult(bucketIndex, previousEntry, null); } - public static void setAtBucket(RubyHash hash, BucketSearchResult bucketSearchResult, Object key, Object value) { - if (bucketSearchResult.getBucket() == null) { - final Bucket bucket = new Bucket(key, value); + public static void setAtBucket(RubyHash hash, HashSearchResult hashSearchResult, Object key, Object value) { + if (hashSearchResult.getEntry() == null) { + final Entry entry = new Entry(key, value); - if (bucketSearchResult.getPreviousBucket() == null) { - ((Bucket[]) hash.getStore())[bucketSearchResult.getIndex()] = bucket; + if (hashSearchResult.getPreviousEntry() == null) { + ((Entry[]) hash.getStore())[hashSearchResult.getIndex()] = entry; } else { - bucketSearchResult.getPreviousBucket().setNextInLookup(bucket); + hashSearchResult.getPreviousEntry().setNextInLookup(entry); } if (hash.getFirstInSequence() == null) { - hash.setFirstInSequence(bucket); - hash.setLastInSequence(bucket); + hash.setFirstInSequence(entry); + hash.setLastInSequence(entry); } else { - hash.getLastInSequence().setNextInSequence(bucket); - bucket.setPreviousInSequence(hash.getLastInSequence()); - hash.setLastInSequence(bucket); + hash.getLastInSequence().setNextInSequence(entry); + entry.setPreviousInSequence(hash.getLastInSequence()); + hash.setLastInSequence(entry); } } else { - final Bucket bucket = bucketSearchResult.getBucket(); + final Entry entry = hashSearchResult.getEntry(); // The bucket stays in the same place in the sequence // Update the key (it overwrites even it it's eql?) and value - bucket.setKey(key); - bucket.setValue(value); + entry.setKey(key); + entry.setValue(value); } } @@ -191,20 +191,20 @@ public static boolean verySlowSetInBuckets(RubyHash hash, Object key, Object val key = DebugOperations.send(hash.getContext(), DebugOperations.send(hash.getContext(), key, "dup", null), "freeze", null); } - final BucketSearchResult bucketSearchResult = verySlowFindBucket(hash, key); - setAtBucket(hash, bucketSearchResult, key, value); - return bucketSearchResult.getBucket() == null; + final HashSearchResult hashSearchResult = verySlowFindBucket(hash, key); + setAtBucket(hash, hashSearchResult, key, value); + return hashSearchResult.getEntry() == null; } @CompilerDirectives.SlowPath - public static void verySlowSetEntries(RubyHash hash, List entries) { - final int size = entries.size(); - hash.setStore(new Bucket[capacityGreaterThan(size)], 0, null, null); + public static void verySlowSetKeyValues(RubyHash hash, List keyValues) { + final int size = keyValues.size(); + hash.setStore(new Entry[capacityGreaterThan(size)], 0, null, null); int actualSize = 0; - for (Entry entry : entries) { - if (verySlowSetInBuckets(hash, entry.getKey(), entry.getValue())) { + for (KeyValue keyValue : keyValues) { + if (verySlowSetInBuckets(hash, keyValue.getKey(), keyValue.getValue())) { actualSize++; } } diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/HashSearchResult.java b/core/src/main/java/org/jruby/truffle/runtime/hash/HashSearchResult.java new file mode 100644 index 00000000000..b2c52661492 --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/HashSearchResult.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 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.hash; + +/** + * The result of looking for an entry (an {@link Entry}) in a Ruby hash. We get the previous entry in the lookup chain + * for this index until the entry was found, the entry that was found, and the index that was used. There are three + * possible outcomes for a search. + *
    + *
  • There is nothing at that index, in which case the entry and previous entry in the chain will be + * {@code null}
  • + *
  • There were entries at that index, but none for our key, in which case the entry will be null, but the + * previous entry will be the last entry in the chain at that index, presumably where we will want to insert our + * new entry
  • + *
  • A entry was found for our key, in which case the entry will be the one correspond to the key, and the + * previous entry will be the one in the entry chain before that one
  • + *
+ */ +public class HashSearchResult { + + private final Entry previousEntry; + private final Entry entry; + private final int index; + + public HashSearchResult(int index, Entry previousEntry, Entry entry) { + this.index = index; + this.previousEntry = previousEntry; + this.entry = entry; + } + + public int getIndex() { + return index; + } + + public Entry getPreviousEntry() { + return previousEntry; + } + + public Entry getEntry() { + return entry; + } + +} diff --git a/core/src/main/java/org/jruby/truffle/runtime/hash/KeyValue.java b/core/src/main/java/org/jruby/truffle/runtime/hash/KeyValue.java new file mode 100644 index 00000000000..3cfb6e42249 --- /dev/null +++ b/core/src/main/java/org/jruby/truffle/runtime/hash/KeyValue.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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.hash; + +/** + * A simple key-value for inserting or retrieving from a hash. + */ +public class KeyValue { + + private final Object key; + private final Object value; + + public KeyValue(Object key, Object value) { + this.key = key; + this.value = value; + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + +} From b2a89e6aa97299cd747c57fefd09264483ca6858 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 15:44:15 +0000 Subject: [PATCH 10/12] [Truffle] We don't need HashSearchResult as a DSL type at all. --- core/src/main/java/org/jruby/truffle/nodes/RubyNode.java | 4 ---- core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java | 1 - 2 files changed, 5 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java index fe38857a8fc..8fe05988475 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyNode.java @@ -191,10 +191,6 @@ public RubyEncodingConverter executeRubyEncodingConverter(VirtualFrame frame) th return RubyTypesGen.RUBYTYPES.expectRubyEncodingConverter(execute(frame)); } - public HashSearchResult executeBucketSearchResult(VirtualFrame frame) throws UnexpectedResultException { - return RubyTypesGen.RUBYTYPES.expectHashSearchResult(execute(frame)); - } - public Dispatch.DispatchAction executeDispatchAction(VirtualFrame frame) { throw new UnsupportedOperationException(); } diff --git a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java index 89e957ddc93..d2276ad0c62 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/RubyTypes.java @@ -61,7 +61,6 @@ RubiniusByteArray.class, // RubyEncodingConverter.class, // RubyBasicObject.class, // - HashSearchResult.class, // Object[].class}) public class RubyTypes { From 639ecf66d75a5f48200d5e8b574f258dd90aeae6 Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 15:53:48 +0000 Subject: [PATCH 11/12] [Truffle] Change hash guard terminology to PackedArray or Buckets --- .../jruby/truffle/nodes/core/HashGuards.java | 4 +- .../jruby/truffle/nodes/core/HashNodes.java | 102 +++++++++--------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java index 4479864f677..91f767cc4b7 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java @@ -18,12 +18,12 @@ public static boolean isNull(RubyHash hash) { return hash.getStore() == null; } - public static boolean isObjectArray(RubyHash hash) { + public static boolean isPackedArray(RubyHash hash) { // Arrays are covariant in Java! return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof Entry[]); } - public static boolean isBucketArray(RubyHash hash) { + public static boolean isBuckets(RubyHash hash) { return hash.getStore() instanceof Entry[]; } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 88dfa224c37..3c84def0974 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -102,8 +102,8 @@ public abstract static class ConstructNode extends HashCoreMethodNode { private final BranchProfile singleObject = new BranchProfile(); private final BranchProfile singleArray = new BranchProfile(); private final BranchProfile objectArray = new BranchProfile(); - private final BranchProfile smallObjectArray = new BranchProfile(); - private final BranchProfile largeObjectArray = new BranchProfile(); + private final BranchProfile smallPackedArray = new BranchProfile(); + private final BranchProfile largePackedArray = new BranchProfile(); private final BranchProfile otherArray = new BranchProfile(); private final BranchProfile singleOther = new BranchProfile(); private final BranchProfile keyValues = new BranchProfile(); @@ -137,7 +137,7 @@ public RubyHash construct(Object[] args) { // TODO(CS): zero length arrays might be a good specialisation if (store.length <= HashOperations.SMALL_HASH_SIZE) { - smallObjectArray.enter(); + smallPackedArray.enter(); final int size = store.length; final Object[] newStore = new Object[HashOperations.SMALL_HASH_SIZE * 2]; @@ -167,7 +167,7 @@ public RubyHash construct(Object[] args) { return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, newStore, size, null); } else { - largeObjectArray.enter(); + largePackedArray.enter(); throw new UnsupportedOperationException(); } } else { @@ -231,8 +231,8 @@ public Object getNull(VirtualFrame frame, RubyHash hash, Object key) { } @ExplodeLoop - @Specialization(guards = "isObjectArray") - public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isPackedArray") + public Object getPackedArray(VirtualFrame frame, RubyHash hash, Object key) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getSize(); @@ -257,8 +257,8 @@ public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) { } - @Specialization(guards = "isBucketArray") - public Object getBucketArray(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isBuckets") + public Object getBuckets(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); final HashSearchResult hashSearchResult = findEntryNode.search(frame, hash, key); @@ -312,8 +312,8 @@ public Object setNull(RubyHash hash, Object key, Object value) { } @ExplodeLoop - @Specialization(guards = "isObjectArray") - public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Object value) { + @Specialization(guards = "isPackedArray") + public Object setPackedArray(VirtualFrame frame, RubyHash hash, Object key, Object value) { hash.checkFrozen(this); final Object[] store = (Object[]) hash.getStore(); @@ -355,8 +355,8 @@ public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Obje return value; } - @Specialization(guards = "isBucketArray") - public Object setBucketArray(RubyHash hash, Object key, Object value) { + @Specialization(guards = "isBuckets") + public Object setBuckets(RubyHash hash, Object key, Object value) { notDesignedForCompilation(); if (HashOperations.verySlowSetInBuckets(hash, key, value)) { @@ -416,8 +416,8 @@ public RubyNilClass deleteNull(RubyHash hash, Object key) { return getContext().getCoreLibrary().getNilObject(); } - @Specialization(guards = "isObjectArray") - public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isPackedArray") + public Object deletePackedArray(VirtualFrame frame, RubyHash hash, Object key) { hash.checkFrozen(this); final Object[] store = (Object[]) hash.getStore(); @@ -439,7 +439,7 @@ public Object deleteObjectArray(VirtualFrame frame, RubyHash hash, Object key) { return getContext().getCoreLibrary().getNilObject(); } - @Specialization(guards = "isBucketArray") + @Specialization(guards = "isBuckets") public Object delete(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); @@ -500,8 +500,8 @@ public RubyHash eachNull(RubyHash hash, RubyProc block) { } @ExplodeLoop - @Specialization(guards = "isObjectArray") - public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isPackedArray") + public RubyHash eachPackedArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); final Object[] store = (Object[]) hash.getStore(); @@ -528,8 +528,8 @@ public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc return hash; } - @Specialization(guards = "isBucketArray") - public RubyHash eachBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isBuckets") + public RubyHash eachBuckets(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { @@ -558,7 +558,7 @@ public boolean emptyNull(RubyHash hash) { } @Specialization(guards = "!isNull") - public boolean emptyObjectArray(RubyHash hash) { + public boolean emptyPackedArray(RubyHash hash) { return hash.getSize() == 0; } @@ -626,8 +626,8 @@ public RubyHash dupNull(RubyHash self, RubyHash from) { return self; } - @Specialization(guards = "isObjectArray(arguments[1])") - public RubyHash dupObjectArray(RubyHash self, RubyHash from) { + @Specialization(guards = "isPackedArray(arguments[1])") + public RubyHash dupPackedArray(RubyHash self, RubyHash from) { notDesignedForCompilation(); if (self == from) { @@ -642,8 +642,8 @@ public RubyHash dupObjectArray(RubyHash self, RubyHash from) { return self; } - @Specialization(guards = "isBucketArray(arguments[1])") - public RubyHash dupBucketArray(RubyHash self, RubyHash from) { + @Specialization(guards = "isBuckets(arguments[1])") + public RubyHash dupBuckets(RubyHash self, RubyHash from) { notDesignedForCompilation(); if (self == from) { @@ -680,7 +680,7 @@ public RubyString inspectNull(RubyHash hash) { } @Specialization - public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) { + public RubyString inspectPackedArray(VirtualFrame frame, RubyHash hash) { notDesignedForCompilation(); final StringBuilder builder = new StringBuilder(); @@ -726,8 +726,8 @@ public boolean keyNull(RubyHash hash, Object key) { return false; } - @Specialization(guards = "isObjectArray") - public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isPackedArray") + public boolean keyPackedArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); final int size = hash.getSize(); @@ -742,8 +742,8 @@ public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) { return false; } - @Specialization(guards = "isBucketArray") - public boolean keyBucketArray(VirtualFrame frame, RubyHash hash, Object key) { + @Specialization(guards = "isBuckets") + public boolean keyBuckets(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); for (KeyValue keyValue : HashOperations.verySlowToKeyValues(hash)) { @@ -773,8 +773,8 @@ public RubyArray keysNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isObjectArray") - public RubyArray keysObjectArray(RubyHash hash) { + @Specialization(guards = "isPackedArray") + public RubyArray keysPackedArray(RubyHash hash) { notDesignedForCompilation(); final Object[] store = (Object[]) hash.getStore(); @@ -788,8 +788,8 @@ public RubyArray keysObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length); } - @Specialization(guards = "isBucketArray") - public RubyArray keysBucketArray(RubyHash hash) { + @Specialization(guards = "isBuckets") + public RubyArray keysBuckets(RubyHash hash) { notDesignedForCompilation(); final Object[] keys = new Object[hash.getSize()]; @@ -821,8 +821,8 @@ public MapNode(MapNode prev) { } @ExplodeLoop - @Specialization(guards = "isObjectArray") - public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isPackedArray") + public RubyArray mapPackedArray(VirtualFrame frame, RubyHash hash, RubyProc block) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getSize(); @@ -852,8 +852,8 @@ public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc bloc return new RubyArray(getContext().getCoreLibrary().getArrayClass(), result, resultSize); } - @Specialization(guards = "isBucketArray") - public RubyArray mapBucketArray(VirtualFrame frame, RubyHash hash, RubyProc block) { + @Specialization(guards = "isBuckets") + public RubyArray mapBuckets(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); final RubyArray array = new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); @@ -890,8 +890,8 @@ public MergeNode(MergeNode prev) { eqlNode = prev.eqlNode; } - @Specialization(guards = {"isObjectArray", "isNull(arguments[1])"}) - public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) { + @Specialization(guards = {"isPackedArray", "isNull(arguments[1])"}) + public RubyHash mergePackedArrayNull(RubyHash hash, RubyHash other) { final Object[] store = (Object[]) hash.getStore(); final Object[] copy = Arrays.copyOf(store, HashOperations.SMALL_HASH_SIZE * 2); @@ -899,8 +899,8 @@ public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) { } @ExplodeLoop - @Specialization(guards = {"isObjectArray", "isObjectArray(arguments[1])"}) - public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, RubyHash other) { + @Specialization(guards = {"isPackedArray", "isPackedArray(arguments[1])"}) + public RubyHash mergePackedArrayPackedArray(VirtualFrame frame, RubyHash hash, RubyHash other) { // TODO(CS): what happens with the default block here? Which side does it get merged from? final Object[] storeA = (Object[]) hash.getStore(); @@ -979,7 +979,7 @@ public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, R @Specialization - public RubyHash mergeBucketArrayBucketArray(RubyHash hash, RubyHash other) { + public RubyHash mergeBucketsBuckets(RubyHash hash, RubyHash other) { final RubyHash merged = new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, new Entry[HashOperations.capacityGreaterThan(hash.getSize() + other.getSize())], 0, null); int size = 0; @@ -1055,7 +1055,7 @@ public int sizeNull(RubyHash hash) { } @Specialization(guards = "!isNull") - public int sizeObjectArray(RubyHash hash) { + public int sizePackedArray(RubyHash hash) { return hash.getSize(); } @@ -1077,8 +1077,8 @@ public RubyArray valuesNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isObjectArray") - public RubyArray valuesObjectArray(RubyHash hash) { + @Specialization(guards = "isPackedArray") + public RubyArray valuesPackedArray(RubyHash hash) { final Object[] store = (Object[]) hash.getStore(); final Object[] values = new Object[hash.getSize()]; @@ -1090,8 +1090,8 @@ public RubyArray valuesObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length); } - @Specialization(guards = "isBucketArray") - public RubyArray valuesBucketArray(RubyHash hash) { + @Specialization(guards = "isBuckets") + public RubyArray valuesBuckets(RubyHash hash) { notDesignedForCompilation(); final Object[] values = new Object[hash.getSize()]; @@ -1128,8 +1128,8 @@ public RubyArray toArrayNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isObjectArray") - public RubyArray toArrayObjectArray(RubyHash hash) { + @Specialization(guards = "isPackedArray") + public RubyArray toArrayPackedArray(RubyHash hash) { notDesignedForCompilation(); final Object[] store = (Object[]) hash.getStore(); @@ -1143,8 +1143,8 @@ public RubyArray toArrayObjectArray(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), pairs, size); } - @Specialization(guards = "isBucketArray") - public RubyArray toArrayBucketArray(RubyHash hash) { + @Specialization(guards = "isBuckets") + public RubyArray toArrayBuckets(RubyHash hash) { notDesignedForCompilation(); final int size = hash.getSize(); From 2bb0e1536e8fa5318e20fd8d1422f18f2e40a7af Mon Sep 17 00:00:00 2001 From: Chris Seaton Date: Tue, 16 Dec 2014 16:07:04 +0000 Subject: [PATCH 12/12] [Truffle] Make the packed array guard for hash, which is complicated by covariance, just the negation of the other two. --- .../jruby/truffle/nodes/core/HashGuards.java | 5 ---- .../jruby/truffle/nodes/core/HashNodes.java | 24 +++++++++---------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java index 91f767cc4b7..4872cde6d83 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashGuards.java @@ -18,11 +18,6 @@ public static boolean isNull(RubyHash hash) { return hash.getStore() == null; } - public static boolean isPackedArray(RubyHash hash) { - // Arrays are covariant in Java! - return hash.getStore() instanceof Object[] && !(hash.getStore() instanceof Entry[]); - } - public static boolean isBuckets(RubyHash hash) { return hash.getStore() instanceof Entry[]; } diff --git a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java index 3c84def0974..c3d1b613c97 100644 --- a/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java +++ b/core/src/main/java/org/jruby/truffle/nodes/core/HashNodes.java @@ -231,7 +231,7 @@ public Object getNull(VirtualFrame frame, RubyHash hash, Object key) { } @ExplodeLoop - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public Object getPackedArray(VirtualFrame frame, RubyHash hash, Object key) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getSize(); @@ -312,7 +312,7 @@ public Object setNull(RubyHash hash, Object key, Object value) { } @ExplodeLoop - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public Object setPackedArray(VirtualFrame frame, RubyHash hash, Object key, Object value) { hash.checkFrozen(this); @@ -416,7 +416,7 @@ public RubyNilClass deleteNull(RubyHash hash, Object key) { return getContext().getCoreLibrary().getNilObject(); } - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public Object deletePackedArray(VirtualFrame frame, RubyHash hash, Object key) { hash.checkFrozen(this); @@ -500,7 +500,7 @@ public RubyHash eachNull(RubyHash hash, RubyProc block) { } @ExplodeLoop - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public RubyHash eachPackedArray(VirtualFrame frame, RubyHash hash, RubyProc block) { notDesignedForCompilation(); @@ -626,7 +626,7 @@ public RubyHash dupNull(RubyHash self, RubyHash from) { return self; } - @Specialization(guards = "isPackedArray(arguments[1])") + @Specialization(guards = {"!isNull(arguments[1])", "!isBuckets(arguments[1])"}) public RubyHash dupPackedArray(RubyHash self, RubyHash from) { notDesignedForCompilation(); @@ -726,7 +726,7 @@ public boolean keyNull(RubyHash hash, Object key) { return false; } - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public boolean keyPackedArray(VirtualFrame frame, RubyHash hash, Object key) { notDesignedForCompilation(); @@ -773,7 +773,7 @@ public RubyArray keysNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public RubyArray keysPackedArray(RubyHash hash) { notDesignedForCompilation(); @@ -821,7 +821,7 @@ public MapNode(MapNode prev) { } @ExplodeLoop - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public RubyArray mapPackedArray(VirtualFrame frame, RubyHash hash, RubyProc block) { final Object[] store = (Object[]) hash.getStore(); final int size = hash.getSize(); @@ -890,7 +890,7 @@ public MergeNode(MergeNode prev) { eqlNode = prev.eqlNode; } - @Specialization(guards = {"isPackedArray", "isNull(arguments[1])"}) + @Specialization(guards = {"!isNull", "!isBuckets", "isNull(arguments[1])"}) public RubyHash mergePackedArrayNull(RubyHash hash, RubyHash other) { final Object[] store = (Object[]) hash.getStore(); final Object[] copy = Arrays.copyOf(store, HashOperations.SMALL_HASH_SIZE * 2); @@ -899,7 +899,7 @@ public RubyHash mergePackedArrayNull(RubyHash hash, RubyHash other) { } @ExplodeLoop - @Specialization(guards = {"isPackedArray", "isPackedArray(arguments[1])"}) + @Specialization(guards = {"!isNull", "!isBuckets", "!isNull(arguments[1])", "!isBuckets(arguments[1])"}) public RubyHash mergePackedArrayPackedArray(VirtualFrame frame, RubyHash hash, RubyHash other) { // TODO(CS): what happens with the default block here? Which side does it get merged from? @@ -1077,7 +1077,7 @@ public RubyArray valuesNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public RubyArray valuesPackedArray(RubyHash hash) { final Object[] store = (Object[]) hash.getStore(); @@ -1128,7 +1128,7 @@ public RubyArray toArrayNull(RubyHash hash) { return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0); } - @Specialization(guards = "isPackedArray") + @Specialization(guards = {"!isNull", "!isBuckets"}) public RubyArray toArrayPackedArray(RubyHash hash) { notDesignedForCompilation();