/*
 * 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 jdk.vm.ci.meta.JavaKind;
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.
/** * 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.
Params:
  • keys – the keys
  • minDensity – the minimum density of the switch table. Used to determine the maximum cardinality of the hash function
Returns:an optional 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<Hasher> forKeys(JavaConstant[] keys, double minDensity) { assert checkKeyKind(keys); if (keys.length <= 2) { return Optional.empty(); } else { int maxCardinality = (int) Math.round(keys.length / minDensity); assert checkIfSorted(keys); TreeSet<Hasher> candidates = new TreeSet<>(new Comparator<Hasher>() { @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(); } } }); int min = keys[0].asInt(); 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 boolean checkKeyKind(JavaConstant[] keys) { for (int i = 0; i < keys.length; i++) { if (keys[i].getJavaKind() != JavaKind.Int) { throw new AssertionError(String.format("Key at index %d is not an int: %s", i, keys[i])); } } return true; } private static boolean checkIfSorted(JavaConstant[] keys) { for (int i = 1; i < keys.length; i++) { if (keys[i - 1].asInt() >= keys[i].asInt()) { throw new AssertionError("Keys array is not sorted"); } } return true; } private static boolean isValid(JavaConstant[] keys, int min, HashFunction function, int cardinality) { Set<Integer> seen = new HashSet<>(keys.length); for (JavaConstant key : keys) { int hash = function.apply(key.asInt(), min) & (cardinality - 1); if (!seen.add(hash)) { return false; } } return true; } private final HashFunction function; private final int cardinality; private final int min; private Hasher(HashFunction function, int cardinality, int min) { this.function = function; this.cardinality = cardinality; this.min = min; }
Applies the hash function.
Params:
  • value – the value to be hashed
Returns:the hash value
/** * Applies the hash function. * * @param value the value to be hashed * @return the hash value */
public int hash(int value) { return function.apply(value, min) & (cardinality - 1); }
Applies the hash function to a lir value.
Params:
  • value – the value to be hashed
  • gen – the lir generator
Returns:the hashed lir value
/** * 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.forInt(min)), gen); return gen.emitAnd(h, gen.getLIRGen().emitJavaConstant(JavaConstant.forInt(cardinality - 1))); }
Returns:the hashing effort
/** * @return the hashing effort */
public int effort() { return function.effort() + 1; }
Returns:the cardinality of the hash function that should match the size of the table switch.
/** * @return the cardinality of the hash function that should match the size of the table switch. */
public int cardinality() { return cardinality; }
Returns:the hash function
/** * @return the hash function */
public HashFunction function() { return function; } @Override public String toString() { return "Hasher[function=" + function + ", effort=" + effort() + ", cardinality=" + cardinality + "]"; } }