git
/
bloom.c
546 строк · 13.4 Кб
1#include "git-compat-util.h"2#include "bloom.h"3#include "diff.h"4#include "diffcore.h"5#include "hashmap.h"6#include "commit-graph.h"7#include "commit.h"8#include "commit-slab.h"9#include "tree.h"10#include "tree-walk.h"11#include "config.h"12#include "repository.h"13
14define_commit_slab(bloom_filter_slab, struct bloom_filter);15
16static struct bloom_filter_slab bloom_filters;17
18struct pathmap_hash_entry {19struct hashmap_entry entry;20const char path[FLEX_ARRAY];21};22
23static uint32_t rotate_left(uint32_t value, int32_t count)24{
25uint32_t mask = 8 * sizeof(uint32_t) - 1;26count &= mask;27return ((value << count) | (value >> ((-count) & mask)));28}
29
30static inline unsigned char get_bitmask(uint32_t pos)31{
32return ((unsigned char)1) << (pos & (BITS_PER_WORD - 1));33}
34
35static int check_bloom_offset(struct commit_graph *g, uint32_t pos,36uint32_t offset)37{
38/*39* Note that we allow offsets equal to the data size, which would set
40* our pointers at one past the end of the chunk memory. This is
41* necessary because the on-disk index points to the end of the
42* entries (so we can compute size by comparing adjacent ones). And
43* naturally the final entry's end is one-past-the-end of the chunk.
44*/
45if (offset <= g->chunk_bloom_data_size - BLOOMDATA_CHUNK_HEADER_SIZE)46return 0;47
48warning("ignoring out-of-range offset (%"PRIuMAX") for changed-path"49" filter at pos %"PRIuMAX" of %s (chunk size: %"PRIuMAX")",50(uintmax_t)offset, (uintmax_t)pos,51g->filename, (uintmax_t)g->chunk_bloom_data_size);52return -1;53}
54
55int load_bloom_filter_from_graph(struct commit_graph *g,56struct bloom_filter *filter,57uint32_t graph_pos)58{
59uint32_t lex_pos, start_index, end_index;60
61while (graph_pos < g->num_commits_in_base)62g = g->base_graph;63
64/* The commit graph commit 'c' lives in doesn't carry Bloom filters. */65if (!g->chunk_bloom_indexes)66return 0;67
68lex_pos = graph_pos - g->num_commits_in_base;69
70end_index = get_be32(g->chunk_bloom_indexes + 4 * lex_pos);71
72if (lex_pos > 0)73start_index = get_be32(g->chunk_bloom_indexes + 4 * (lex_pos - 1));74else75start_index = 0;76
77if (check_bloom_offset(g, lex_pos, end_index) < 0 ||78check_bloom_offset(g, lex_pos - 1, start_index) < 0)79return 0;80
81if (end_index < start_index) {82warning("ignoring decreasing changed-path index offsets"83" (%"PRIuMAX" > %"PRIuMAX") for positions"84" %"PRIuMAX" and %"PRIuMAX" of %s",85(uintmax_t)start_index, (uintmax_t)end_index,86(uintmax_t)(lex_pos-1), (uintmax_t)lex_pos,87g->filename);88return 0;89}90
91filter->len = end_index - start_index;92filter->data = (unsigned char *)(g->chunk_bloom_data +93sizeof(unsigned char) * start_index +94BLOOMDATA_CHUNK_HEADER_SIZE);95filter->version = g->bloom_filter_settings->hash_version;96filter->to_free = NULL;97
98return 1;99}
100
101/*
102* Calculate the murmur3 32-bit hash value for the given data
103* using the given seed.
104* Produces a uniformly distributed hash value.
105* Not considered to be cryptographically secure.
106* Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm
107*/
108uint32_t murmur3_seeded_v2(uint32_t seed, const char *data, size_t len)109{
110const uint32_t c1 = 0xcc9e2d51;111const uint32_t c2 = 0x1b873593;112const uint32_t r1 = 15;113const uint32_t r2 = 13;114const uint32_t m = 5;115const uint32_t n = 0xe6546b64;116int i;117uint32_t k1 = 0;118const char *tail;119
120int len4 = len / sizeof(uint32_t);121
122uint32_t k;123for (i = 0; i < len4; i++) {124uint32_t byte1 = (uint32_t)(unsigned char)data[4*i];125uint32_t byte2 = ((uint32_t)(unsigned char)data[4*i + 1]) << 8;126uint32_t byte3 = ((uint32_t)(unsigned char)data[4*i + 2]) << 16;127uint32_t byte4 = ((uint32_t)(unsigned char)data[4*i + 3]) << 24;128k = byte1 | byte2 | byte3 | byte4;129k *= c1;130k = rotate_left(k, r1);131k *= c2;132
133seed ^= k;134seed = rotate_left(seed, r2) * m + n;135}136
137tail = (data + len4 * sizeof(uint32_t));138
139switch (len & (sizeof(uint32_t) - 1)) {140case 3:141k1 ^= ((uint32_t)(unsigned char)tail[2]) << 16;142/*-fallthrough*/143case 2:144k1 ^= ((uint32_t)(unsigned char)tail[1]) << 8;145/*-fallthrough*/146case 1:147k1 ^= ((uint32_t)(unsigned char)tail[0]) << 0;148k1 *= c1;149k1 = rotate_left(k1, r1);150k1 *= c2;151seed ^= k1;152break;153}154
155seed ^= (uint32_t)len;156seed ^= (seed >> 16);157seed *= 0x85ebca6b;158seed ^= (seed >> 13);159seed *= 0xc2b2ae35;160seed ^= (seed >> 16);161
162return seed;163}
164
165static uint32_t murmur3_seeded_v1(uint32_t seed, const char *data, size_t len)166{
167const uint32_t c1 = 0xcc9e2d51;168const uint32_t c2 = 0x1b873593;169const uint32_t r1 = 15;170const uint32_t r2 = 13;171const uint32_t m = 5;172const uint32_t n = 0xe6546b64;173int i;174uint32_t k1 = 0;175const char *tail;176
177int len4 = len / sizeof(uint32_t);178
179uint32_t k;180for (i = 0; i < len4; i++) {181uint32_t byte1 = (uint32_t)data[4*i];182uint32_t byte2 = ((uint32_t)data[4*i + 1]) << 8;183uint32_t byte3 = ((uint32_t)data[4*i + 2]) << 16;184uint32_t byte4 = ((uint32_t)data[4*i + 3]) << 24;185k = byte1 | byte2 | byte3 | byte4;186k *= c1;187k = rotate_left(k, r1);188k *= c2;189
190seed ^= k;191seed = rotate_left(seed, r2) * m + n;192}193
194tail = (data + len4 * sizeof(uint32_t));195
196switch (len & (sizeof(uint32_t) - 1)) {197case 3:198k1 ^= ((uint32_t)tail[2]) << 16;199/*-fallthrough*/200case 2:201k1 ^= ((uint32_t)tail[1]) << 8;202/*-fallthrough*/203case 1:204k1 ^= ((uint32_t)tail[0]) << 0;205k1 *= c1;206k1 = rotate_left(k1, r1);207k1 *= c2;208seed ^= k1;209break;210}211
212seed ^= (uint32_t)len;213seed ^= (seed >> 16);214seed *= 0x85ebca6b;215seed ^= (seed >> 13);216seed *= 0xc2b2ae35;217seed ^= (seed >> 16);218
219return seed;220}
221
222void fill_bloom_key(const char *data,223size_t len,224struct bloom_key *key,225const struct bloom_filter_settings *settings)226{
227int i;228const uint32_t seed0 = 0x293ae76f;229const uint32_t seed1 = 0x7e646e2c;230uint32_t hash0, hash1;231if (settings->hash_version == 2) {232hash0 = murmur3_seeded_v2(seed0, data, len);233hash1 = murmur3_seeded_v2(seed1, data, len);234} else {235hash0 = murmur3_seeded_v1(seed0, data, len);236hash1 = murmur3_seeded_v1(seed1, data, len);237}238
239key->hashes = (uint32_t *)xcalloc(settings->num_hashes, sizeof(uint32_t));240for (i = 0; i < settings->num_hashes; i++)241key->hashes[i] = hash0 + i * hash1;242}
243
244void clear_bloom_key(struct bloom_key *key)245{
246FREE_AND_NULL(key->hashes);247}
248
249void add_key_to_filter(const struct bloom_key *key,250struct bloom_filter *filter,251const struct bloom_filter_settings *settings)252{
253int i;254uint64_t mod = filter->len * BITS_PER_WORD;255
256for (i = 0; i < settings->num_hashes; i++) {257uint64_t hash_mod = key->hashes[i] % mod;258uint64_t block_pos = hash_mod / BITS_PER_WORD;259
260filter->data[block_pos] |= get_bitmask(hash_mod);261}262}
263
264void init_bloom_filters(void)265{
266init_bloom_filter_slab(&bloom_filters);267}
268
269static void free_one_bloom_filter(struct bloom_filter *filter)270{
271if (!filter)272return;273free(filter->to_free);274}
275
276void deinit_bloom_filters(void)277{
278deep_clear_bloom_filter_slab(&bloom_filters, free_one_bloom_filter);279}
280
281static int pathmap_cmp(const void *hashmap_cmp_fn_data UNUSED,282const struct hashmap_entry *eptr,283const struct hashmap_entry *entry_or_key,284const void *keydata UNUSED)285{
286const struct pathmap_hash_entry *e1, *e2;287
288e1 = container_of(eptr, const struct pathmap_hash_entry, entry);289e2 = container_of(entry_or_key, const struct pathmap_hash_entry, entry);290
291return strcmp(e1->path, e2->path);292}
293
294static void init_truncated_large_filter(struct bloom_filter *filter,295int version)296{
297filter->data = filter->to_free = xmalloc(1);298filter->data[0] = 0xFF;299filter->len = 1;300filter->version = version;301}
302
303#define VISITED (1u<<21)304#define HIGH_BITS (1u<<22)305
306static int has_entries_with_high_bit(struct repository *r, struct tree *t)307{
308if (parse_tree(t))309return 1;310
311if (!(t->object.flags & VISITED)) {312struct tree_desc desc;313struct name_entry entry;314
315init_tree_desc(&desc, &t->object.oid, t->buffer, t->size);316while (tree_entry(&desc, &entry)) {317size_t i;318for (i = 0; i < entry.pathlen; i++) {319if (entry.path[i] & 0x80) {320t->object.flags |= HIGH_BITS;321goto done;322}323}324
325if (S_ISDIR(entry.mode)) {326struct tree *sub = lookup_tree(r, &entry.oid);327if (sub && has_entries_with_high_bit(r, sub)) {328t->object.flags |= HIGH_BITS;329goto done;330}331}332
333}334
335done:336t->object.flags |= VISITED;337}338
339return !!(t->object.flags & HIGH_BITS);340}
341
342static int commit_tree_has_high_bit_paths(struct repository *r,343struct commit *c)344{
345struct tree *t;346if (repo_parse_commit(r, c))347return 1;348t = repo_get_commit_tree(r, c);349if (!t)350return 1;351return has_entries_with_high_bit(r, t);352}
353
354static struct bloom_filter *upgrade_filter(struct repository *r, struct commit *c,355struct bloom_filter *filter,356int hash_version)357{
358struct commit_list *p = c->parents;359if (commit_tree_has_high_bit_paths(r, c))360return NULL;361
362if (p && commit_tree_has_high_bit_paths(r, p->item))363return NULL;364
365filter->version = hash_version;366
367return filter;368}
369
370struct bloom_filter *get_bloom_filter(struct repository *r, struct commit *c)371{
372struct bloom_filter *filter;373int hash_version;374
375filter = get_or_compute_bloom_filter(r, c, 0, NULL, NULL);376if (!filter)377return NULL;378
379prepare_repo_settings(r);380hash_version = r->settings.commit_graph_changed_paths_version;381
382if (!(hash_version == -1 || hash_version == filter->version))383return NULL; /* unusable filter */384return filter;385}
386
387struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,388struct commit *c,389int compute_if_not_present,390const struct bloom_filter_settings *settings,391enum bloom_filter_computed *computed)392{
393struct bloom_filter *filter;394int i;395struct diff_options diffopt;396
397if (computed)398*computed = BLOOM_NOT_COMPUTED;399
400if (!bloom_filters.slab_size)401return NULL;402
403filter = bloom_filter_slab_at(&bloom_filters, c);404
405if (!filter->data) {406uint32_t graph_pos;407if (repo_find_commit_pos_in_graph(r, c, &graph_pos))408load_bloom_filter_from_graph(r->objects->commit_graph,409filter, graph_pos);410}411
412if (filter->data && filter->len) {413struct bloom_filter *upgrade;414if (!settings || settings->hash_version == filter->version)415return filter;416
417/* version mismatch, see if we can upgrade */418if (compute_if_not_present &&419git_env_bool("GIT_TEST_UPGRADE_BLOOM_FILTERS", 1)) {420upgrade = upgrade_filter(r, c, filter,421settings->hash_version);422if (upgrade) {423if (computed)424*computed |= BLOOM_UPGRADED;425return upgrade;426}427}428}429if (!compute_if_not_present)430return NULL;431
432repo_diff_setup(r, &diffopt);433diffopt.flags.recursive = 1;434diffopt.detect_rename = 0;435diffopt.max_changes = settings->max_changed_paths;436diff_setup_done(&diffopt);437
438/* ensure commit is parsed so we have parent information */439repo_parse_commit(r, c);440
441if (c->parents)442diff_tree_oid(&c->parents->item->object.oid, &c->object.oid, "", &diffopt);443else444diff_tree_oid(NULL, &c->object.oid, "", &diffopt);445diffcore_std(&diffopt);446
447if (diff_queued_diff.nr <= settings->max_changed_paths) {448struct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL);449struct pathmap_hash_entry *e;450struct hashmap_iter iter;451
452for (i = 0; i < diff_queued_diff.nr; i++) {453const char *path = diff_queued_diff.queue[i]->two->path;454
455/*456* Add each leading directory of the changed file, i.e. for
457* 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so
458* the Bloom filter could be used to speed up commands like
459* 'git log dir/subdir', too.
460*
461* Note that directories are added without the trailing '/'.
462*/
463do {464char *last_slash = strrchr(path, '/');465
466FLEX_ALLOC_STR(e, path, path);467hashmap_entry_init(&e->entry, strhash(path));468
469if (!hashmap_get(&pathmap, &e->entry, NULL))470hashmap_add(&pathmap, &e->entry);471else472free(e);473
474if (!last_slash)475last_slash = (char*)path;476*last_slash = '\0';477
478} while (*path);479
480diff_free_filepair(diff_queued_diff.queue[i]);481}482
483if (hashmap_get_size(&pathmap) > settings->max_changed_paths) {484init_truncated_large_filter(filter,485settings->hash_version);486if (computed)487*computed |= BLOOM_TRUNC_LARGE;488goto cleanup;489}490
491filter->len = (hashmap_get_size(&pathmap) * settings->bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD;492filter->version = settings->hash_version;493if (!filter->len) {494if (computed)495*computed |= BLOOM_TRUNC_EMPTY;496filter->len = 1;497}498CALLOC_ARRAY(filter->data, filter->len);499filter->to_free = filter->data;500
501hashmap_for_each_entry(&pathmap, &iter, e, entry) {502struct bloom_key key;503fill_bloom_key(e->path, strlen(e->path), &key, settings);504add_key_to_filter(&key, filter, settings);505clear_bloom_key(&key);506}507
508cleanup:509hashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry);510} else {511for (i = 0; i < diff_queued_diff.nr; i++)512diff_free_filepair(diff_queued_diff.queue[i]);513init_truncated_large_filter(filter, settings->hash_version);514
515if (computed)516*computed |= BLOOM_TRUNC_LARGE;517}518
519if (computed)520*computed |= BLOOM_COMPUTED;521
522free(diff_queued_diff.queue);523DIFF_QUEUE_CLEAR(&diff_queued_diff);524
525return filter;526}
527
528int bloom_filter_contains(const struct bloom_filter *filter,529const struct bloom_key *key,530const struct bloom_filter_settings *settings)531{
532int i;533uint64_t mod = filter->len * BITS_PER_WORD;534
535if (!mod)536return -1;537
538for (i = 0; i < settings->num_hashes; i++) {539uint64_t hash_mod = key->hashes[i] % mod;540uint64_t block_pos = hash_mod / BITS_PER_WORD;541if (!(filter->data[block_pos] & get_bitmask(hash_mod)))542return 0;543}544
545return 1;546}
547