Backed out 3 changesets (bug 1197866) for javascript crashes at SimpleLongOpenHashSet.
authorCosmin Sabou <csabou@mozilla.com>
Wed, 24 Oct 2018 02:06:40 +0300
changeset 490976 4fc03405a420a93d5dec32f09fd8323369b9ba7c
parent 490975 85cfe824ab6a8c80cd73faa101dcf9ddffe0db89
child 490977 b0efa0731412c1fef077a02c3683a90a36352218
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
bugs1197866
milestone65.0a1
backs out32db311632da28ba478fe6d25f39ebbd365f7ae7
a80113dba9c130824111bb48f9fde2b9c4406fcd
7b9befdce34d9afd8c754ea561dca35da11a87fd
Backed out 3 changesets (bug 1197866) for javascript crashes at SimpleLongOpenHashSet. Backed out changeset 32db311632da (bug 1197866) Backed out changeset a80113dba9c1 (bug 1197866) Backed out changeset 7b9befdce34d (bug 1197866)
mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
mobile/android/app/src/test/java/org/mozilla/gecko/SimpleLongOpenHashSetTest.java
mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
mobile/android/base/java/org/mozilla/gecko/SimpleLongOpenHashSet.java
mobile/android/thirdparty/com/github/yonik/MurmurHash3.java
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
@@ -1,15 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko;
 
-import java.lang.ref.SoftReference;
-
 import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.RemoteException;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -105,29 +103,16 @@ public class GlobalPageMetadataTest {
 
             assertPageMetadataCountForGUID(0, "guid2", pageMetadataClient);
 
         } finally {
             provider.shutdown();
         }
     }
 
-    @Test
-    public void testGlobalHistory() throws Exception {
-        GlobalHistory history = GlobalHistory.getInstance();
-        // Hold reference to prevent racing with GC.
-        SimpleLongOpenHashSet set = new SimpleLongOpenHashSet();
-        history.mVisitedCache = new SoftReference<>(set);
-        String exampleDotCom = "http://example.com/";
-        assertFalse(history.containsUri(exampleDotCom));
-        history.addUri(exampleDotCom);
-        assertTrue(history.containsUri(exampleDotCom));
-        assertFalse(history.containsUri(exampleDotCom + "2.html"));
-    }
-
     /**
      * Expects cursor to be at the correct position.
      */
     private void assertCursorValues(Cursor cursor, String json, int hasImage, String guid) {
         assertNotNull(cursor);
         assertEquals(json, cursor.getString(cursor.getColumnIndexOrThrow(PageMetadata.JSON)));
         assertEquals(hasImage, cursor.getInt(cursor.getColumnIndexOrThrow(PageMetadata.HAS_IMAGE)));
         assertEquals(guid, cursor.getString(cursor.getColumnIndexOrThrow(PageMetadata.HISTORY_GUID)));
deleted file mode 100644
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/SimpleLongOpenHashSetTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.gecko;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-public final class SimpleLongOpenHashSetTest {
-    @Test
-    public void testEmpty() {
-        SimpleLongOpenHashSet set = new SimpleLongOpenHashSet();
-
-        assertFalse(set.contains(42));
-        assertFalse(set.contains(0));
-        assertFalse(set.contains(-1));
-        assertTrue(set.size() == 0);
-    }
-
-    @Test
-    public void testOneElement() {
-        SimpleLongOpenHashSet set = new SimpleLongOpenHashSet();
-        assertTrue(set.add(42));
-        assertFalse(set.add(42));
-
-        assertTrue(set.contains(42));
-        assertFalse(set.contains(1));
-        assertFalse(set.contains(0));
-        assertFalse(set.contains(-1));
-        assertTrue(set.size() == 1);
-    }
-
-    @Test
-    public void testTwoElementsSameBucket() {
-        SimpleLongOpenHashSet set = new SimpleLongOpenHashSet(4);
-        assertTrue(set.add(0));
-        assertTrue(set.add(4));
-
-        assertTrue(set.contains(0));
-        assertTrue(set.contains(4));
-        assertFalse(set.contains(8));
-        assertTrue(set.size() == 2);
-    }
-
-    @Test
-    public void testManyElementsResize() {
-        SimpleLongOpenHashSet set = new SimpleLongOpenHashSet(4);
-        int count = 16;
-        for (int i = 0; i < count; ++i) {
-            assertTrue(set.add(i));
-        }
-
-        for (int i = 0; i < count; ++i) {
-            assertTrue(set.contains(i));
-        }
-        assertTrue(set.size() == count);
-    }
-
-    @Test
-    public void testConstructorEdgeCases() {
-        for (int i = 0; i < 3; ++i) {
-            SimpleLongOpenHashSet set = new SimpleLongOpenHashSet(i);
-            assertTrue(set.add(42));
-            assertFalse(set.add(42));
-
-            assertTrue(set.contains(42));
-            assertFalse(set.contains(1));
-            assertFalse(set.contains(0));
-            assertFalse(set.contains(-1));
-            assertTrue(set.size() == 1);
-        }
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
@@ -1,28 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import java.lang.ref.SoftReference;
-import java.security.NoSuchAlgorithmException;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.Queue;
-
-import com.github.yonik.MurmurHash3;
+import java.util.Set;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Log;
@@ -47,108 +44,85 @@ class GlobalHistory {
     // this allows batching together multiple requests and processing them together,
     // which is more efficient.
     private static final long BATCHING_DELAY_MS = 100;
 
     private final Handler mHandler;                     // a background thread on which we can process requests
 
     //  Note: These fields are accessed through the NotificationRunnable inner class.
     final Queue<String> mPendingUris;           // URIs that need to be checked
-
-    // This set can grow to tens of thousands of entries and several megabytes
-    // of overhead when using HashSet<String>.  Compact this by storing
-    // a 64-bit hash instead of the original String.
-    SoftReference<SimpleLongOpenHashSet> mVisitedCache;  // cache of the visited URI list
-
+    SoftReference<Set<String>> mVisitedCache;   // cache of the visited URI list
     boolean mProcessing; // = false             // whether or not the runnable is queued/working
 
     private class NotifierRunnable implements Runnable {
         private final ContentResolver mContentResolver;
         private final BrowserDB mDB;
 
         public NotifierRunnable(final Context context) {
             mContentResolver = context.getContentResolver();
             mDB = BrowserDB.from(context);
         }
 
         @Override
         public void run() {
-            MurmurHash3.LongPair pair = new MurmurHash3.LongPair();
-            SimpleLongOpenHashSet visitedSet = mVisitedCache.get();
+            Set<String> visitedSet = mVisitedCache.get();
             if (visitedSet == null) {
                 // The cache was wiped. Repopulate it.
                 Log.w(LOGTAG, "Rebuilding visited link set...");
                 final long start = SystemClock.uptimeMillis();
                 final Cursor c = mDB.getAllVisitedHistory(mContentResolver);
                 if (c == null) {
                     return;
                 }
 
                 try {
-                    visitedSet = new SimpleLongOpenHashSet(c.getCount());
+                    visitedSet = new HashSet<String>();
                     if (c.moveToFirst()) {
                         do {
-                            visitedSet.add(hashUrl(c.getBlob(0), pair));
+                            visitedSet.add(c.getString(0));
                         } while (c.moveToNext());
                     }
-                    mVisitedCache = new SoftReference<SimpleLongOpenHashSet>(visitedSet);
+                    mVisitedCache = new SoftReference<Set<String>>(visitedSet);
                     final long end = SystemClock.uptimeMillis();
                     final long took = end - start;
-                    Log.i(LOGTAG, "Rebuilt visited link set containing " + visitedSet.size() + " URLs in " + took + " ms");
                     Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK, (int) Math.min(took, Integer.MAX_VALUE));
                 } finally {
                     c.close();
                 }
             }
 
             // This runs on the same handler thread as the checkUriVisited code,
             // so no synchronization is needed.
             while (true) {
                 final String uri = mPendingUris.poll();
                 if (uri == null) {
                     break;
                 }
 
-                if (visitedSet.contains(hashUrl(uri.getBytes(StringUtils.UTF_8), pair))) {
+                if (visitedSet.contains(uri)) {
                     GeckoAppShell.notifyUriVisited(uri);
                 }
             }
 
             mProcessing = false;
         }
     };
 
     private GlobalHistory() {
         mHandler = ThreadUtils.getBackgroundHandler();
         mPendingUris = new LinkedList<String>();
-        mVisitedCache = new SoftReference<SimpleLongOpenHashSet>(null);
+        mVisitedCache = new SoftReference<Set<String>>(null);
     }
 
     public void addToGeckoOnly(String uri) {
-        addUri(uri);
-        GeckoAppShell.notifyUriVisited(uri);
-    }
-
-    // visible for testing
-    void addUri(String uri) {
-        MurmurHash3.LongPair pair = new MurmurHash3.LongPair();
-        SimpleLongOpenHashSet visitedSet = mVisitedCache.get();
+        Set<String> visitedSet = mVisitedCache.get();
         if (visitedSet != null) {
-            visitedSet.add(hashUrl(uri.getBytes(StringUtils.UTF_8), pair));
+            visitedSet.add(uri);
         }
-    }
-
-    // visible for testing
-    boolean containsUri(String uri) {
-        MurmurHash3.LongPair pair = new MurmurHash3.LongPair();
-        SimpleLongOpenHashSet visitedSet = mVisitedCache.get();
-        if (visitedSet == null) {
-            return false;
-        }
-        return visitedSet.contains(hashUrl(uri.getBytes(StringUtils.UTF_8), pair));
+        GeckoAppShell.notifyUriVisited(uri);
     }
 
     public void add(final Context context, final BrowserDB db, String uri) {
         ThreadUtils.assertOnBackgroundThread();
         final long start = SystemClock.uptimeMillis();
 
         // stripAboutReaderUrl only removes about:reader if present, in all other cases the original string is returned
         final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
@@ -227,15 +201,9 @@ class GlobalHistory {
         });
     }
 
     private void dispatchUriAvailableMessage(String uri) {
         final GeckoBundle message = new GeckoBundle();
         message.putString(EVENT_PARAM_URI, uri);
         EventDispatcher.getInstance().dispatch(EVENT_URI_AVAILABLE_IN_HISTORY, message);
     }
-
-    /** Calculate hash of input. */
-    private static long hashUrl(byte[] input, MurmurHash3.LongPair pair) {
-        MurmurHash3.murmurhash3_x64_128(input, 0, input.length, 0, pair);
-        return pair.val1 ^ pair.val2;
-    }
 }
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/SimpleLongOpenHashSet.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.Arrays;
-
-/**
- * A simple open hash table, specialied for primitive long values.
- *
- * There is no support for removing individual values but clearing the entire
- * set is supported.
- *
- * The implementation uses linear probing.
- */
-
-public final class SimpleLongOpenHashSet {
-    private long[] elems = new long[0];
-    private int size;
-    private static final int FILL_PERCENTAGE = 70;
-    /** Use a known value to indicate that an element is not present. */
-    private static final long SENTINEL = ~0L;
-    private static final int MINIMUM_SIZE = 16;
-
-    public SimpleLongOpenHashSet() {
-        this(MINIMUM_SIZE);
-    }
-
-    public SimpleLongOpenHashSet(int expectedSize) {
-        if (expectedSize <= MINIMUM_SIZE) {
-            expectedSize = MINIMUM_SIZE;
-        }
-        resize(expectedSize);
-    }
-
-    /** @return true if element exists in set */
-    public boolean contains(long elem) {
-        if (elem == SENTINEL) {
-            ++elem;
-        }
-        int index = index(elem);
-        // loop guaranteed to terminate since set has at least one SENTINEL
-        while (true) {
-            if (elems[index] == elem) {
-                return true;
-            } else if (elems[index] == -1) {
-                return false;
-            } else {
-                ++index;
-                if (index >= elems.length) {
-                    index = 0;
-                }
-            }
-        }
-    }
-
-    /**
-     * Add an element to the hash set.
-     *
-     * @return true if element did not previously exist
-     */
-    public boolean add(long elem) {
-        if ((size + 1) * 100 / elems.length > FILL_PERCENTAGE) {
-            resize(2 * elems.length);
-        }
-        if (elem == SENTINEL) {
-            ++elem;
-        }
-        int index = index(elem);
-        // loop guaranteed to terminate since set has at least one SENTINEL
-        while (true) {
-            if (elems[index] == elem) {
-                return false;
-            } else if (elems[index] == -1) {
-                elems[index] = elem;
-                ++size;
-                return true;
-            } else {
-                ++index;
-                if (index >= elems.length) {
-                    index = 0;
-                }
-            }
-        }
-    }
-
-    /** Remove all elements from the set. */
-    public void clear() {
-        Arrays.fill(elems, SENTINEL);
-        size = 0;
-    }
-
-    /** @return number of elements in the set. */
-    public int size() {
-        return size;
-    }
-
-    private void resize(int size) {
-        long[] oldElems = elems;
-        elems = new long[size];
-        clear();
-        for (long elem : oldElems) {
-            add(elem);
-        }
-    }
-
-    /** @return valid index for element */
-    private int index(long elem) {
-        // remove sign bit since modulus preserves it
-        return (int) Math.abs(elem) % elems.length;
-    }
-}
deleted file mode 100644
--- a/mobile/android/thirdparty/com/github/yonik/MurmurHash3.java
+++ /dev/null
@@ -1,304 +0,0 @@
-package com.github.yonik;
-
-/**
- *  MurmurHash3 - a non-cryptographic hash function intended for applications
- *  which can tolerate collisions.
- *  <p>
- *  The MurmurHash3 algorithm was created by Austin Appleby and placed in the public domain.
- *  This java port was authored by Yonik Seeley and also placed into the public domain.
- *  The author hereby disclaims copyright to this source code.
- *  <p>
- *  This produces exactly the same hash values as the final C++
- *  version of MurmurHash3 and is thus suitable for producing the same hash values across
- *  platforms.
- *  <p>
- *  The 32 bit x86 version of this hash should be the fastest variant for relatively short keys like ids.
- *  murmurhash3_x64_128 is a good choice for longer strings or if you need more than 32 bits of hash.
- *  <p>
- *  Note - The x86 and x64 versions do _not_ produce the same results, as the
- *  algorithms are optimized for their respective platforms.
- *  <p>
- *  See http://github.com/yonik/java_util for future updates to this file.
- */
-public final class MurmurHash3 {
-
-  /** 128 bits of state */
-  public static final class LongPair {
-    public long val1;
-    public long val2;
-  }
-
-  public static final int fmix32(int h) {
-    h ^= h >>> 16;
-    h *= 0x85ebca6b;
-    h ^= h >>> 13;
-    h *= 0xc2b2ae35;
-    h ^= h >>> 16;
-    return h;
-  }
-
-  public static final long fmix64(long k) {
-    k ^= k >>> 33;
-    k *= 0xff51afd7ed558ccdL;
-    k ^= k >>> 33;
-    k *= 0xc4ceb9fe1a85ec53L;
-    k ^= k >>> 33;
-    return k;
-  }
-
-  /** Gets a long from a byte buffer in little endian byte order. */
-  public static final long getLongLittleEndian(byte[] buf, int offset) {
-    return     ((long)buf[offset+7]    << 56)   // no mask needed
-            | ((buf[offset+6] & 0xffL) << 48)
-            | ((buf[offset+5] & 0xffL) << 40)
-            | ((buf[offset+4] & 0xffL) << 32)
-            | ((buf[offset+3] & 0xffL) << 24)
-            | ((buf[offset+2] & 0xffL) << 16)
-            | ((buf[offset+1] & 0xffL) << 8)
-            | ((buf[offset  ] & 0xffL));        // no shift needed
-  }
-
-
-  /** Returns the MurmurHash3_x86_32 hash. */
-  @SuppressWarnings("fallthrough")
-  public static int murmurhash3_x86_32(byte[] data, int offset, int len, int seed) {
-
-    final int c1 = 0xcc9e2d51;
-    final int c2 = 0x1b873593;
-
-    int h1 = seed;
-    int roundedEnd = offset + (len & 0xfffffffc);  // round down to 4 byte block
-
-    for (int i=offset; i<roundedEnd; i+=4) {
-      // little endian load order
-      int k1 = (data[i] & 0xff) | ((data[i+1] & 0xff) << 8) | ((data[i+2] & 0xff) << 16) | (data[i+3] << 24);
-      k1 *= c1;
-      k1 = (k1 << 15) | (k1 >>> 17);  // ROTL32(k1,15);
-      k1 *= c2;
-
-      h1 ^= k1;
-      h1 = (h1 << 13) | (h1 >>> 19);  // ROTL32(h1,13);
-      h1 = h1*5+0xe6546b64;
-    }
-
-    // tail
-    int k1 = 0;
-
-    switch(len & 0x03) {
-      case 3:
-        k1 = (data[roundedEnd + 2] & 0xff) << 16;
-        // fallthrough
-      case 2:
-        k1 |= (data[roundedEnd + 1] & 0xff) << 8;
-        // fallthrough
-      case 1:
-        k1 |= (data[roundedEnd] & 0xff);
-        k1 *= c1;
-        k1 = (k1 << 15) | (k1 >>> 17);  // ROTL32(k1,15);
-        k1 *= c2;
-        h1 ^= k1;
-    }
-
-    // finalization
-    h1 ^= len;
-
-    // fmix(h1);
-    h1 ^= h1 >>> 16;
-    h1 *= 0x85ebca6b;
-    h1 ^= h1 >>> 13;
-    h1 *= 0xc2b2ae35;
-    h1 ^= h1 >>> 16;
-
-    return h1;
-  }
-
-
-  /** Returns the MurmurHash3_x86_32 hash of the UTF-8 bytes of the String without actually encoding
-   * the string to a temporary buffer.  This is more than 2x faster than hashing the result
-   * of String.getBytes().
-   */
-  public static int murmurhash3_x86_32(CharSequence data, int offset, int len, int seed) {
-
-    final int c1 = 0xcc9e2d51;
-    final int c2 = 0x1b873593;
-
-    int h1 = seed;
-
-    int pos = offset;
-    int end = offset + len;
-    int k1 = 0;
-    int k2 = 0;
-    int shift = 0;
-    int bits = 0;
-    int nBytes = 0;   // length in UTF8 bytes
-
-
-    while (pos < end) {
-      int code = data.charAt(pos++);
-      if (code < 0x80) {
-        k2 = code;
-        bits = 8;
-
-        /***
-        // optimized ascii implementation (currently slower!!! code size?)
-        if (shift == 24) {
-          k1 = k1 | (code << 24);
-
-          k1 *= c1;
-          k1 = (k1 << 15) | (k1 >>> 17);  // ROTL32(k1,15);
-          k1 *= c2;
-
-          h1 ^= k1;
-          h1 = (h1 << 13) | (h1 >>> 19);  // ROTL32(h1,13);
-          h1 = h1*5+0xe6546b64;
-
-          shift = 0;
-          nBytes += 4;
-          k1 = 0;
-        } else {
-          k1 |= code << shift;
-          shift += 8;
-        }
-        continue;
-       ***/
-
-      }
-      else if (code < 0x800) {
-        k2 = (0xC0 | (code >> 6))
-                | ((0x80 | (code & 0x3F)) << 8);
-        bits = 16;
-      }
-      else if (code < 0xD800 || code > 0xDFFF || pos>=end) {
-        // we check for pos>=end to encode an unpaired surrogate as 3 bytes.
-        k2 = (0xE0 | (code >> 12))
-                | ((0x80 | ((code >> 6) & 0x3F)) << 8)
-                | ((0x80 | (code & 0x3F)) << 16);
-        bits = 24;
-      } else {
-        // surrogate pair
-        // int utf32 = pos < end ? (int) data.charAt(pos++) : 0;
-        int utf32 = (int) data.charAt(pos++);
-        utf32 = ((code - 0xD7C0) << 10) + (utf32 & 0x3FF);
-        k2 = (0xff & (0xF0 | (utf32 >> 18)))
-             | ((0x80 | ((utf32 >> 12) & 0x3F))) << 8
-             | ((0x80 | ((utf32 >> 6) & 0x3F))) << 16
-             |  (0x80 | (utf32 & 0x3F)) << 24;
-        bits = 32;
-      }
-
-
-      k1 |= k2 << shift;
-
-      // int used_bits = 32 - shift;  // how many bits of k2 were used in k1.
-      // int unused_bits = bits - used_bits; //  (bits-(32-shift)) == bits+shift-32  == bits-newshift
-
-      shift += bits;
-      if (shift >= 32) {
-        // mix after we have a complete word
-
-        k1 *= c1;
-        k1 = (k1 << 15) | (k1 >>> 17);  // ROTL32(k1,15);
-        k1 *= c2;
-
-        h1 ^= k1;
-        h1 = (h1 << 13) | (h1 >>> 19);  // ROTL32(h1,13);
-        h1 = h1*5+0xe6546b64;
-
-        shift -= 32;
-        // unfortunately, java won't let you shift 32 bits off, so we need to check for 0
-        if (shift != 0) {
-          k1 = k2 >>> (bits-shift);   // bits used == bits - newshift
-        } else {
-          k1 = 0;
-        }
-        nBytes += 4;
-      }
-
-    } // inner
-
-    // handle tail
-    if (shift > 0) {
-      nBytes += shift >> 3;
-      k1 *= c1;
-      k1 = (k1 << 15) | (k1 >>> 17);  // ROTL32(k1,15);
-      k1 *= c2;
-      h1 ^= k1;
-    }
-
-    // finalization
-    h1 ^= nBytes;
-
-    // fmix(h1);
-    h1 ^= h1 >>> 16;
-    h1 *= 0x85ebca6b;
-    h1 ^= h1 >>> 13;
-    h1 *= 0xc2b2ae35;
-    h1 ^= h1 >>> 16;
-
-    return h1;
-  }
-
-
-  /** Returns the MurmurHash3_x64_128 hash, placing the result in "out". */
-  @SuppressWarnings("fallthrough")
-  public static void murmurhash3_x64_128(byte[] key, int offset, int len, int seed, LongPair out) {
-    // The original algorithm does have a 32 bit unsigned seed.
-    // We have to mask to match the behavior of the unsigned types and prevent sign extension.
-    long h1 = seed & 0x00000000FFFFFFFFL;
-    long h2 = seed & 0x00000000FFFFFFFFL;
-
-    final long c1 = 0x87c37b91114253d5L;
-    final long c2 = 0x4cf5ad432745937fL;
-
-    int roundedEnd = offset + (len & 0xFFFFFFF0);  // round down to 16 byte block
-    for (int i=offset; i<roundedEnd; i+=16) {
-        long k1 = getLongLittleEndian(key, i);
-        long k2 = getLongLittleEndian(key, i+8);
-        k1 *= c1; k1  = Long.rotateLeft(k1,31); k1 *= c2; h1 ^= k1;
-        h1 = Long.rotateLeft(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
-        k2 *= c2; k2  = Long.rotateLeft(k2,33); k2 *= c1; h2 ^= k2;
-        h2 = Long.rotateLeft(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
-    }
-
-    long k1 = 0;
-    long k2 = 0;
-
-    switch (len & 15) {
-      case 15: k2  = (key[roundedEnd+14] & 0xffL) << 48;
-      case 14: k2 |= (key[roundedEnd+13] & 0xffL) << 40;
-      case 13: k2 |= (key[roundedEnd+12] & 0xffL) << 32;
-      case 12: k2 |= (key[roundedEnd+11] & 0xffL) << 24;
-      case 11: k2 |= (key[roundedEnd+10] & 0xffL) << 16;
-      case 10: k2 |= (key[roundedEnd+ 9] & 0xffL) << 8;
-      case  9: k2 |= (key[roundedEnd+ 8] & 0xffL);
-        k2 *= c2; k2  = Long.rotateLeft(k2, 33); k2 *= c1; h2 ^= k2;
-      case  8: k1  = ((long)key[roundedEnd+7]) << 56;
-      case  7: k1 |= (key[roundedEnd+6] & 0xffL) << 48;
-      case  6: k1 |= (key[roundedEnd+5] & 0xffL) << 40;
-      case  5: k1 |= (key[roundedEnd+4] & 0xffL) << 32;
-      case  4: k1 |= (key[roundedEnd+3] & 0xffL) << 24;
-      case  3: k1 |= (key[roundedEnd+2] & 0xffL) << 16;
-      case  2: k1 |= (key[roundedEnd+1] & 0xffL) << 8;
-      case  1: k1 |= (key[roundedEnd  ] & 0xffL);
-        k1 *= c1; k1  = Long.rotateLeft(k1,31); k1 *= c2; h1 ^= k1;
-    }
-
-    //----------
-    // finalization
-
-    h1 ^= len; h2 ^= len;
-
-    h1 += h2;
-    h2 += h1;
-
-    h1 = fmix64(h1);
-    h2 = fmix64(h2);
-
-    h1 += h2;
-    h2 += h1;
-
-    out.val1 = h1;
-    out.val2 = h2;
-  }
-
-}