--- /dev/null 2017-11-16 08:17:56.803999947 +0100 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.lir/src/org/graalvm/compiler/lir/hashing/Hasher.java 2019-03-12 08:10:49.768053599 +0100 @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package org.graalvm.compiler.lir.hashing; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; + +import org.graalvm.compiler.lir.gen.ArithmeticLIRGenerator; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.Value; + +/** + * This class holds a hash function at a specific cardinality and min value (lowest key). The + * cardinality is the required size of the hash table to make the hasher injective for the provided + * keys. + */ +public final class Hasher { + + /** + * Tries to find a hash function without conflicts for the provided keys. + * + * @param keys the keys + * @param minDensity the minimum density of the switch table. Used to determine the maximum + * cardinality of the hash function + * @return an optional hasher + */ + public static Optional forKeys(JavaConstant[] keys, double minDensity) { + if (keys.length <= 2) { + return Optional.empty(); + } else { + int maxCardinality = (int) Math.round(keys.length / minDensity); + assertSorted(keys); + TreeSet candidates = new TreeSet<>(new Comparator() { + @Override + public int compare(Hasher o1, Hasher o2) { + int d = o1.cardinality - o2.cardinality; + if (d != 0) { + return d; + } else { + return o1.effort() - o2.effort(); + } + } + }); + long min = keys[0].asLong(); + for (HashFunction f : HashFunction.instances()) { + for (int cardinality = keys.length; cardinality < maxCardinality; cardinality++) { + if (isValid(keys, min, f, cardinality)) { + candidates.add(new Hasher(f, cardinality, min)); + break; + } + } + } + if (candidates.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(candidates.first()); + } + } + } + + private static void assertSorted(JavaConstant[] keys) { + for (int i = 1; i < keys.length; i++) { + assert keys[i - 1].asLong() < keys[i].asLong(); + } + } + + private static boolean isValid(JavaConstant[] keys, long min, HashFunction function, int cardinality) { + Set seen = new HashSet<>(keys.length); + for (JavaConstant key : keys) { + int hash = function.apply(key.asLong(), min) & (cardinality - 1); + if (!seen.add(hash)) { + return false; + } + } + return true; + } + + private final HashFunction function; + private final int cardinality; + private final long min; + + private Hasher(HashFunction function, int cardinality, long min) { + this.function = function; + this.cardinality = cardinality; + this.min = min; + } + + /** + * Applies the hash function. + * + * @param value the value to be hashed + * @return the hash value + */ + public int hash(long value) { + return function.apply(value, min) & (cardinality - 1); + } + + /** + * Applies the hash function to a lir value. + * + * @param value the value to be hashed + * @param gen the lir generator + * @return the hashed lir value + */ + public Value hash(Value value, ArithmeticLIRGenerator gen) { + Value h = function.gen(value, gen.getLIRGen().emitJavaConstant(JavaConstant.forLong(min)), gen); + return gen.emitAnd(h, gen.getLIRGen().emitJavaConstant(JavaConstant.forInt(cardinality - 1))); + } + + /** + * @return the hashing effort + */ + public int effort() { + return function.effort() + 1; + } + + /** + * @return the cardinality of the hash function that should match the size of the table switch. + */ + public int cardinality() { + return cardinality; + } + + /** + * @return the hash function + */ + public HashFunction function() { + return function; + } + + @Override + public String toString() { + return "Hasher[function=" + function + ", effort=" + effort() + ", cardinality=" + cardinality + "]"; + } +}