Welcome to little lamb

Code » limb » master » tree

[master] / src / liblimb / hlookup.h / hlookup.c

/* This file is part of limb                           https://lila.oss/limb
 * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
/* Based on hashlittle2 (lookup3)
 * Copyright (C) 2006 Bob Jenkins -- Public Domain */
/* SPDX-License-Identifier: ISC */
#include <limb/hlookup.h>
#include "config.h"

#if LIMB_ENDIAN == LIMB_LITTLE
# define HASH_LITTLE_ENDIAN 1
#else
# define HASH_LITTLE_ENDIAN 0
#endif

/* This is hashlittle2() by Bob Jenkins, May 2006, Public Domain
 *
 * These are functions for producing 32-bit hashes for hash table lookup.
 * You can use this free for any purpose. It's in the public domain. It has no
 * warranty.
 *
 * If you want to find a hash of, say, exactly 7 integers, do
 * a = i1;  b = i2;  c = i3;
 * mix(a,b,c);
 * a += i4; b += i5; c += i6;
 * mix(a,b,c);
 * a += i7;
 * final(a,b,c);
 * then use c as the hash value.
 *
 * Why is this so big?  I read 12 bytes at a time into 3 4-byte integers,
 * then mix those integers. This is fast (you can do a lot more thorough
 * mixing with 12*3 instructions on 3 integers than you can with 3 instructions
 * on 1 byte), but shoehorning those bytes into integers efficiently is messy.
 */


#define rot(x,k)    ( ((x) << (k)) | ( (x) >> (32 - (k)) ) )

/* mix 3 32-bit values reversibly.
 *
 * This is reversible, so any information in (a,b,c) before mix() is still in
 * (a,b,c) after mix().
 *
 * If four pairs of (a,b,c) inputs are run through mix(), or through mix() in
 * reverse, there are at least 32 bits of the output that are sometimes the same
 * for one pair and different for another pair.  This was tested for:
 * * pairs that differed by one bit, by two bits, in any combination of top bits
 *   of (a,b,c), or in any combination of bottom bits of (a,b,c).
 * * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed the
 * output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly
 * produced by subtraction) look like a single 1-bit difference.
 * * the base values were pseudorandom, all zero but one bit set, or all zero
 *   plus a counter that starts at zero.
 *
 * Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that satisfy this
 * are :
 *   4  6  8 16 19  4
 *   9 15  3 18 27 15
 *  14  9  3  7 17  3
 * Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing for "differ" defined
 * as + with a one-bit base and a two-bit delta.  I used
 * http://burtleburtle.net/bob/hash/avalanche.html to choose the operations,
 * constants, and arrangements of the variables.
 *
 * This does not achieve avalanche.  There are input bits of (a,b,c) that fail
 * to affect some output bits of (a,b,c), especially of a.  The most thoroughly
 * mixed value is c, but it doesn't really even achieve avalanche in c.
 *
 * This allows some parallelism.  Read-after-writes are good at doubling the
 * number of bits affected, so the goal of mixing pulls in the opposite
 * direction as the goal of parallelism.  I did what I could.  Rotates seem to
 * cost as much as shifts on every machine I could lay my hands on, and rotates
 * are much kinder to the top and bottom bits, so I used rotates.
 */
#define mix(a,b,c) \
{ \
    a -= c;  a ^= rot(c, 4);  c += b; \
    b -= a;  b ^= rot(a, 6);  a += c; \
    c -= b;  c ^= rot(b, 8);  b += a; \
    a -= c;  a ^= rot(c,16);  c += b; \
    b -= a;  b ^= rot(a,19);  a += c; \
    c -= b;  c ^= rot(b, 4);  b += a; \
}

/* final mixing of 3 32-bit values (a,b,c) into c
 *
 * Pairs of (a,b,c) values differing in only a few bits will usually produce
 * values of c that look totally different.  This was tested for * pairs that
 * differed by one bit, by two bits, in any combination of top bits of (a,b,c),
 * or in any combination of bottom bits of (a,b,c).
 * * "differ" is defined as +, -, ^, or ~^.  For + and -, I transformed the
 *   output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly
 *   produced by subtraction) look like a single 1-bit difference.
 * * the base values were pseudorandom, all zero but one bit set, or all zero
 *   plus a counter that starts at zero.
 *
 * These constants passed:
 *  14 11 25 16 4 14 24
 *  12 14 25 16 4 14 24
 * and these came close:
 *   4  8 15 26 3 22 24
 *  10  8 15 26 3 22 24
 *  11  8 15 26 3 22 24
 */
#define final(a,b,c) \
{ \
    c ^= b; c -= rot(b,14); \
    a ^= c; a -= rot(c,11); \
    b ^= a; b -= rot(a,25); \
    c ^= b; c -= rot(b,16); \
    a ^= c; a -= rot(c, 4); \
    b ^= a; b -= rot(a,14); \
    c ^= b; c -= rot(b,24); \
}


void
hlookup(u32 *main, u32 *second, const void *data, size_t dlen)
{
    u32 a, b, c;
    /* needed for Mac Powerbook G4 */
    union { const void *ptr; size_t i; } u;

    /* Set up the internal state */
    a = b = c = 0xdeadbeef + (u32) dlen + *main;
    if (second)
        c += *second;

    u.ptr = data;
    if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
        /* read 32bit chunks */
        const u32 *k = (const u32 *) data;

        /* all but last block: aligned reads and affect 32 bits of (a,b,c) */
        while (dlen > 12) {
            a += k[0];
            b += k[1];
            c += k[2];
            mix(a,b,c);
            dlen -= 12;
            k += 3;
        }

        /* handle the last (probably partial) block */
#ifndef VALGRIND
        /* "k[2] & 0x00ffffff" actually reads beyond the end of the data, but
         * then masks off the part it's not allowed to read.  Because the data
         * is aligned, the masked-off tail is in the same word as the rest of
         * it.  Every machine with memory protection I've seen does it on word
         * boundaries, so is OK with this.  But VALGRIND will still catch it and
         * complain.  The masking trick does make the hash noticably faster for
         * short data (e.g. English words).
         */
        switch(dlen) {
            case 12: c += k[2];              b += k[1];              a += k[0];
                break;
            case 11: c += k[2] & 0x00ffffff; b += k[1];              a += k[0];
                break;
            case 10: c += k[2] & 0x0000ffff; b += k[1];              a += k[0];
                break;
            case  9: c += k[2] & 0x000000ff; b += k[1];              a += k[0];
                break;
            case  8:                         b += k[1];              a += k[0];
                break;
            case  7:                         b += k[1] & 0x00ffffff; a += k[0];
                break;
            case  6:                         b += k[1] & 0x0000ffff; a += k[0];
                break;
            case  5:                         b += k[1] & 0x000000ff; a += k[0];
                break;
            case  4:                                                 a += k[0];
                break;
            case  3:                                                 a += k[0] & 0x00ffffff;
                break;
            case  2:                                                 a += k[0] & 0x0000ffff;
                break;
            case  1:                                                 a += k[0] & 0x000000ff;
                break;
            case  0:
                /* zero length strings require no mixing */
                *main = c;
                if (second)
                    *second = b;
                return;
        }
#else
        /* make valgrind happy - read 8bit chunks */
        const u8 *k8 = (const u8 *) k;
        switch(dlen) {
            case 12: c += k[2];             b += k[1];              a += k[0];
                break;
            case 11: c += ((u32) k8[10]) << 16;
                /* fall through */
            case 10: c += ((u32) k8[ 9]) <<  8;
                /* fall through */
            case  9: c += k8[8];
                /* fall through */
            case  8:                        b += k[1];              a += k[0];
                break;
            case  7:                        b += ((u32) k8[6]) << 16;
                /* fall through */
            case  6:                        b += ((u32) k8[5]) <<  8;
                /* fall through */
            case  5:                        b += k8[4];
                /* fall through */
            case  4:                                                a += k[0];
                break;
            case  3:                                                a += ((u32) k8[2]) << 16;
                /* fall through */
            case  2:                                                a += ((u32) k8[1]) <<  8;
                /* fall through */
            case  1:                                                a += k8[0];
                break;
            case  0:
                /* zero length strings require no mixing */
                *main = c;
                if (second)
                    *second = b;
                return;
        }
#endif /* VALGRIND */
    } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
        /* read 16-bit chunks */
        const u16 *k = (const u16 *) data;
        const u8  *k8;

        /* all but last block: aligned reads and different mixing */
        while (dlen > 12)
        {
            a += k[0] + (((u32) k[1]) << 16);
            b += k[2] + (((u32) k[3]) << 16);
            c += k[4] + (((u32) k[5]) << 16);
            mix(a,b,c);
            dlen -= 12;
            k += 6;
        }

        /* handle the last (probably partial) block */
        k8 = (const u8 *) k;
        switch(dlen) {
            case 12: c += k[4] + (((u32) k[5]) << 16);
                     b += k[2] + (((u32) k[3]) << 16);
                     a += k[0] + (((u32) k[1]) << 16);
                     break;
            case 11: c += ((u32) k8[10]) << 16;
                     /* fall through */
            case 10: c += k[4];
                     b += k[2] + (((u32) k[3]) << 16);
                     a += k[0] + (((u32) k[1]) << 16);
                     break;
            case 9 : c += k8[8];
                     /* fall through */
            case 8 : b += k[2] + (((u32) k[3]) << 16);
                     a += k[0] + (((u32) k[1]) << 16);
                     break;
            case 7 : b += ((u32) k8[6]) << 16;
                     /* fall through */
            case 6 : b += k[2];
                     a += k[0] + (((u32) k[1]) << 16);
                     break;
            case 5 : b += k8[4];
                     /* fall through */
            case 4 : a += k[0] + (((u32) k[1]) << 16);
                     break;
            case 3 : a += ((u32) k8[2]) << 16;
                     /* fall through */
            case 2 : a += k[0];
                     break;
            case 1 : a += k8[0];
                     break;
            case 0 :
                /* zero length strings require no mixing */
                *main = c;
                if (second)
                    *second = b;
                return;
        }
    } else {
        /* need to read the key one byte at a time */
        const u8 *k = (const u8 *) data;

        /* all but the last block: affect some 32 bits of (a,b,c) */
        while (dlen > 12)
        {
            a += k[0];
            a += ((u32) k[ 1]) <<  8;
            a += ((u32) k[ 2]) << 16;
            a += ((u32) k[ 3]) << 24;
            b += k[4];
            b += ((u32) k[ 5]) <<  8;
            b += ((u32) k[ 6]) << 16;
            b += ((u32) k[ 7]) << 24;
            c += k[8];
            c += ((u32) k[ 9]) <<  8;
            c += ((u32) k[10]) << 16;
            c += ((u32) k[11]) << 24;
            mix(a,b,c);
            dlen -= 12;
            k += 12;
        }

        /* last block: affect all 32 bits of (c) */
        switch(dlen) {
            case 12: c += ((u32) k[11]) << 24; /* fallthrough */
            case 11: c += ((u32) k[10]) << 16; /* fallthrough */
            case 10: c += ((u32) k[ 9]) <<  8; /* fallthrough */
            case  9: c +=        k[ 8];        /* fallthrough */
            case  8: b += ((u32) k[ 7]) << 24; /* fallthrough */
            case  7: b += ((u32) k[ 6]) << 16; /* fallthrough */
            case  6: b += ((u32) k[ 5]) <<  8; /* fallthrough */
            case  5: b +=        k[ 4];        /* fallthrough */
            case  4: a += ((u32) k[ 3]) << 24; /* fallthrough */
            case  3: a += ((u32) k[ 2]) << 16; /* fallthrough */
            case  2: a += ((u32) k[ 1]) <<  8; /* fallthrough */
            case  1: a +=        k[ 0];
                break;
            case 0 :
                /* zero length strings require no mixing */
                *main = c;
                if (second)
                    *second = b;
                return;
        }
    }

    final(a,b,c);
    *main = c;
    if (second)
        *second = b;
}