/* Copyright (C) 2020 Free Software Foundation, Inc. Contributed by Nicolas Koenig This file is part of the GNU Fortran Native Coarray Library (libnca). Libnca is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Libnca 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 for more details. Under Section 7 of GPL version 3, you are granted additional permissions described in the GCC Runtime Library Exception, version 3.1, as published by the Free Software Foundation. You should have received a copy of the GNU General Public License and a copy of the GCC Runtime Library Exception along with this program; see the files COPYING3 and COPYING.RUNTIME respectively. If not, see . */ #include "libgfortran.h" #include "shared_memory.h" #include "allocator.h" #include "hashmap.h" #include #define INITIAL_BITNUM (5) #define INITIAL_SIZE (1 << INITIAL_BITNUM) #define CRITICAL_LOOKAHEAD (16) static ssize_t n_ent; typedef struct { memid id; shared_mem_ptr p; /* If p == SHMPTR_NULL, the entry is empty. */ size_t s; int max_lookahead; int refcnt; } hashmap_entry; static ssize_t num_entries (hashmap_entry *data, size_t size) { size_t i; ssize_t ret = 0; for (i = 0; i < size; i++) { if (!SHMPTR_IS_NULL (data[i].p)) ret++; } return ret; } /* 64 bit to 64 bit hash function. */ /* static inline uint64_t hash (uint64_t x) { return x * 11400714819323198485lu; } */ #define ASSERT_HM(hm, cond) assert_hashmap (hm, cond, #cond) static void assert_hashmap (hashmap *hm, bool asserted, const char *cond) { if (!asserted) { dprintf (2, cond); dump_hm (hm); } assert (asserted); } static inline uint64_t hash (uint64_t key) { key ^= (key >> 30); key *= 0xbf58476d1ce4e5b9ul; key ^= (key >> 27); key *= 0x94d049bb133111ebul; key ^= (key >> 31); return key; } /* Gets a pointer to the current data in the hashmap. */ static inline hashmap_entry * get_data (hashmap *hm) { return SHMPTR_AS (hashmap_entry *, hm->s->data, hm->sm); } /* Generate mask from current number of bits. */ static inline intptr_t gen_mask (hashmap *hm) { return (1 << hm->s->bitnum) - 1; } /* Add with wrap-around at hashmap size. */ static inline size_t hmiadd (hashmap *hm, size_t s, ssize_t o) { return (s + o) & gen_mask (hm); } /* Get the expected offset for entry id. */ static inline ssize_t get_expected_offset (hashmap *hm, memid id) { return hash (id) >> (PTR_BITS - hm->s->bitnum); } /* Initialize the hashmap. */ void hashmap_init (hashmap *hm, hashmap_shared *hs, allocator *a, shared_memory *mem) { hashmap_entry *data; hm->s = hs; hm->sm = mem; hm->s->data = shared_malloc (a, INITIAL_SIZE * sizeof (hashmap_entry)); data = get_data (hm); memset (data, '\0', INITIAL_SIZE * sizeof (hashmap_entry)); for (int i = 0; i < INITIAL_SIZE; i++) data[i].p = SHMPTR_NULL; hm->s->size = INITIAL_SIZE; hm->s->bitnum = INITIAL_BITNUM; hm->a = a; } /* This checks if the entry id exists in that range the range between the expected position and the maximum lookahead. */ static ssize_t scan_inside_lookahead (hashmap *hm, ssize_t expected_off, memid id) { ssize_t lookahead; hashmap_entry *data; data = get_data (hm); lookahead = data[expected_off].max_lookahead; ASSERT_HM (hm, lookahead < CRITICAL_LOOKAHEAD); for (int i = 0; i <= lookahead; i++) /* For performance, this could iterate backwards. */ if (data[hmiadd (hm, expected_off, i)].id == id) return hmiadd (hm, expected_off, i); return -1; } /* Scan for the next empty slot we can use. Returns offset relative to the expected position. */ static ssize_t scan_empty (hashmap *hm, ssize_t expected_off) { hashmap_entry *data; data = get_data (hm); for (int i = 0; i < CRITICAL_LOOKAHEAD; i++) if (SHMPTR_IS_NULL (data[hmiadd (hm, expected_off, i)].p)) return i; return -1; } /* Search the hashmap for id. */ hashmap_search_result hashmap_get (hashmap *hm, memid id) { hashmap_search_result ret; hashmap_entry *data; size_t expected_offset; ssize_t res; data = get_data (hm); expected_offset = get_expected_offset (hm, id); res = scan_inside_lookahead (hm, expected_offset, id); if (res != -1) ret = ((hashmap_search_result){ .p = data[res].p, .size = data[res].s, .res_offset = res }); else ret.p = SHMPTR_NULL; return ret; } /* Return size of a hashmap search result. */ size_t hm_search_result_size (hashmap_search_result *res) { return res->size; } /* Return pointer of a hashmap search result. */ shared_mem_ptr hm_search_result_ptr (hashmap_search_result *res) { return res->p; } /* Return pointer of a hashmap search result. */ bool hm_search_result_contains (hashmap_search_result *res) { return !SHMPTR_IS_NULL (res->p); } /* Enlarge hashmap memory. */ static void enlarge_hashmap_mem (hashmap *hm, hashmap_entry **data, bool f) { shared_mem_ptr old_data_p; size_t old_size; old_data_p = hm->s->data; old_size = hm->s->size; hm->s->data = shared_malloc (hm->a, (hm->s->size *= 2) * sizeof (hashmap_entry)); hm->s->bitnum++; *data = get_data (hm); for (size_t i = 0; i < hm->s->size; i++) (*data)[i] = ((hashmap_entry){ .id = 0, .p = SHMPTR_NULL, .s = 0, .max_lookahead = 0, .refcnt = 0 }); if (f) shared_free (hm->a, old_data_p, old_size); } /* Resize hashmap. */ static void resize_hm (hashmap *hm, hashmap_entry **data) { shared_mem_ptr old_data_p; hashmap_entry *old_data, *new_data; size_t old_size; ssize_t new_offset, inital_index, new_index; memid id; ssize_t max_lookahead; /* old_data points to the old block containing the hashmap. We redistribute the data from there into the new block. */ old_data_p = hm->s->data; old_data = *data; old_size = hm->s->size; enlarge_hashmap_mem (hm, &new_data, false); retry_resize: for (size_t i = 0; i < old_size; i++) { if (SHMPTR_IS_NULL (old_data[i].p)) continue; id = old_data[i].id; inital_index = get_expected_offset (hm, id); new_offset = scan_empty (hm, inital_index); /* If we didn't find a free slot, just resize the hashmap again. */ if (new_offset == -1) { enlarge_hashmap_mem (hm, &new_data, true); goto retry_resize; /* Sue me. */ } ASSERT_HM (hm, new_offset < CRITICAL_LOOKAHEAD); new_index = hmiadd (hm, inital_index, new_offset); max_lookahead = new_data[inital_index].max_lookahead; new_data[inital_index].max_lookahead = new_offset > max_lookahead ? new_offset : max_lookahead; new_data[new_index] = ((hashmap_entry){ .id = id, .p = old_data[i].p, .s = old_data[i].s, .max_lookahead = new_data[new_index].max_lookahead, .refcnt = old_data[i].refcnt }); } shared_free (hm->a, old_data_p, old_size); *data = new_data; } /* Set an entry in the hashmap. */ void hashmap_set (hashmap *hm, memid id, hashmap_search_result *hsr, shared_mem_ptr p, size_t size) { hashmap_entry *data; ssize_t expected_offset, lookahead; ssize_t empty_offset; ssize_t delta; data = get_data (hm); if (hsr) { data[hsr->res_offset].s = size; data[hsr->res_offset].p = p; return; } expected_offset = get_expected_offset (hm, id); while ((delta = scan_empty (hm, expected_offset)) == -1) { resize_hm (hm, &data); expected_offset = get_expected_offset (hm, id); } empty_offset = hmiadd (hm, expected_offset, delta); lookahead = data[expected_offset].max_lookahead; data[expected_offset].max_lookahead = delta > lookahead ? delta : lookahead; data[empty_offset] = ((hashmap_entry){ .id = id, .p = p, .s = size, .max_lookahead = data[empty_offset].max_lookahead, .refcnt = 1 }); n_ent++; /* TODO: Shouldn't reset refcnt, but this doesn't matter at the moment because of the way the function is used. */ } /* Change the refcount of a hashmap entry. */ static int hashmap_change_refcnt (hashmap *hm, memid id, hashmap_search_result *res, int delta) { hashmap_entry *data; hashmap_search_result r; hashmap_search_result *pr; int ret; hashmap_entry *entry; data = get_data (hm); if (res) pr = res; else { r = hashmap_get (hm, id); pr = &r; } entry = &data[pr->res_offset]; ret = (entry->refcnt += delta); if (ret == 0) { n_ent--; entry->id = 0; entry->p = SHMPTR_NULL; entry->s = 0; } return ret; } /* Increase hashmap entry refcount. */ void hashmap_inc (hashmap *hm, memid id, hashmap_search_result *res) { int ret; ret = hashmap_change_refcnt (hm, id, res, 1); ASSERT_HM (hm, ret > 0); } /* Decrease hashmap entry refcount. */ int hashmap_dec (hashmap *hm, memid id, hashmap_search_result *res) { int ret; ret = hashmap_change_refcnt (hm, id, res, -1); ASSERT_HM (hm, ret >= 0); return ret; } #define PE(str, ...) fprintf (stderr, INDENT str, ##__VA_ARGS__) #define INDENT "" void dump_hm (hashmap *hm) { hashmap_entry *data; size_t exp; size_t occ_num = 0; PE ("h %p (size: %lu, bitnum: %d)\n", hm, hm->s->size, hm->s->bitnum); data = get_data (hm); fprintf (stderr, "offset = %p data = %p\n", (unsigned long)hm->s->data.p, data); #undef INDENT #define INDENT " " for (size_t i = 0; i < hm->s->size; i++) { exp = get_expected_offset (hm, data[i].id); if (!SHMPTR_IS_NULL (data[i].p)) { PE ("%2lu. (exp: %2lu w la %d) id %#-16lx p %#-14p s %-7lu -- la " "%u ref %u %-16p\n", i, exp, data[exp].max_lookahead, data[i].id, data[i].p.p, data[i].s, data[i].max_lookahead, data[i].refcnt, data + i); occ_num++; } else PE ("%2lu. empty -- la %u " " %p\n", i, data[i].max_lookahead, data + i); } #undef INDENT #define INDENT "" PE ("occupancy: %lu %f\n", occ_num, ((double)occ_num) / hm->s->size); }