Make default table removal behavior deterministic, but have caches

explicitly use random eviction.
This commit is contained in:
Bruce Hill 2024-08-03 17:42:45 -04:00
parent 68b34cf00b
commit 8357d02992
4 changed files with 32 additions and 57 deletions

View File

@ -297,12 +297,9 @@ public void Table$remove(table_t *t, const void *key, const TypeInfo *type)
// TODO: this work doesn't need to be done if the key is already missing
maybe_copy_on_write(t, type);
// If unspecified, pop a random key:
if (!key) {
hdebug("Popping random key\n");
uint32_t index = arc4random_uniform(t->entries.length);
key = GET_ENTRY(*t, index);
}
// If unspecified, pop the last key:
if (!key)
key = GET_ENTRY(*t, t->entries.length-1);
// Steps: look up the bucket for the removed key
// If missing, then return immediately
@ -396,6 +393,13 @@ public void Table$clear(table_t *t)
memset(t, 0, sizeof(table_t));
}
public table_t Table$sorted(table_t t, const TypeInfo *type)
{
closure_t cmp = (closure_t){.fn=generic_compare, .userdata=(void*)type->TableInfo.key};
array_t entries = Array$sorted(t.entries, cmp, entry_size(type));
return Table$from_entries(entries, type);
}
public bool Table$equal(const table_t *x, const table_t *y, const TypeInfo *type)
{
assert(type->tag == TableInfo);
@ -408,26 +412,7 @@ public bool Table$equal(const table_t *x, const table_t *y, const TypeInfo *type
if ((x->fallback != NULL) != (y->fallback != NULL))
return false;
const TypeInfo *value_type = type->TableInfo.value;
for (int64_t i = 0, length = Table$length(*x); i < length; i++) {
void *x_key = GET_ENTRY(*x, i);
void *x_value = x_key + value_offset(type);
void *y_value = Table$get_raw(*y, x_key, type);
if (!y_value)
return false;
if (!generic_equal(x_value, y_value, value_type))
return false;
}
if (x->default_value && y->default_value
&& !generic_equal(x->default_value, y->default_value, value_type))
return false;
if (x->fallback && y->fallback
&& !Table$equal(x->fallback, y->fallback, type))
return false;
return true;
return (Table$compare(x, y, type) == 0);
}
public int32_t Table$compare(const table_t *x, const table_t *y, const TypeInfo *type)
@ -439,12 +424,9 @@ public int32_t Table$compare(const table_t *x, const table_t *y, const TypeInfo
else if (x->entries.length != y->entries.length)
return (x->entries.length > y->entries.length) - (x->entries.length < y->entries.length);
closure_t cmp = (closure_t){.fn=generic_compare, .userdata=(void*)table.key};
array_t x_entries = Array$sorted(x->entries, cmp, entry_size(type));
array_t y_entries = Array$sorted(y->entries, cmp, entry_size(type));
for (int64_t i = 0; i < x_entries.length; i++) {
void *x_key = x_entries.data + x_entries.stride * i;
void *y_key = y_entries.data + y_entries.stride * i;
for (int64_t i = 0; i < x->entries.length; i++) {
void *x_key = x->entries.data + x->entries.stride * i;
void *y_key = y->entries.data + y->entries.stride * i;
int32_t diff = generic_compare(x_key, y_key, table.key);
if (diff != 0) return diff;
void *x_value = x_key + value_offset(type);
@ -473,30 +455,14 @@ public uint32_t Table$hash(const table_t *t, const TypeInfo *type)
{
assert(type->tag == TableInfo);
// Table hashes are computed as:
// hash(#t, xor(hash(k) for k in t.keys), xor(hash(v) for v in t.values), hash(t.fallback), hash(t.default))
// hash(hash(t.keys), hash(t.values), hash(t.fallback), hash(t.default))
// Where fallback and default hash to zero if absent
auto table = type->TableInfo;
int64_t val_off = value_offset(type);
uint32_t key_hashes = 0, value_hashes = 0, fallback_hash = 0, default_hash = 0;
for (int64_t i = 0, length = Table$length(*t); i < length; i++) {
void *entry = GET_ENTRY(*t, i);
key_hashes ^= generic_hash(entry, table.key);
value_hashes ^= generic_hash(entry + val_off, table.value);
}
if (t->fallback)
fallback_hash = Table$hash(t->fallback, type);
if (t->default_value)
default_hash = generic_hash(t->default_value, table.value);
struct { int64_t len; uint32_t k, v, f, d; } components = {
Table$length(*t),
key_hashes,
value_hashes,
fallback_hash,
default_hash,
uint32_t components[] = {
Array$hash(&t->entries, $ArrayInfo(table.key)),
Array$hash(&t->entries + value_offset(type), $ArrayInfo(table.value)),
t->fallback ? Table$hash(t->fallback, type) : 0,
t->default_value ? generic_hash(t->default_value, table.value) : 0,
};
uint32_t hash;
halfsiphash(&components, sizeof(components), TOMO_HASH_KEY, (uint8_t*)&hash, sizeof(hash));

View File

@ -39,6 +39,7 @@ void Table$set(table_t *t, const void *key, const void *value, const TypeInfo *t
#define Table$reserve_value(t, key_expr, type) ({ __typeof(key_expr) k = key_expr; Table$reserve(t, &k, NULL, type); })
void Table$remove(table_t *t, const void *key, const TypeInfo *type);
void Table$clear(table_t *t);
table_t Table$sorted(table_t t, const TypeInfo *type);
void Table$mark_copy_on_write(table_t *t);
int32_t Table$compare(const table_t *x, const table_t *y, const TypeInfo *type);
bool Table$equal(const table_t *x, const table_t *y, const TypeInfo *type);

View File

@ -621,7 +621,10 @@ CORD compile_statement(env_t *env, ast_t *ast)
body = CORD_asprintf("{\n%r\n}", body);
env->code->funcs = CORD_all(env->code->funcs, code, " ", body, "\n");
if (fndef->cache && fndef->cache->tag == Int && Match(fndef->cache, Int)->i > 0) {
if (fndef->cache && fndef->cache->tag == Int) {
int64_t cache_size = Match(fndef->cache, Int)->i;
if (cache_size <= 0)
code_err(fndef->cache, "Cache sizes must be greater than 0");
const char *arg_type_name = heap_strf("%s$args", Match(fndef->name, Var)->name);
ast_t *args_def = FakeAST(StructDef, .name=arg_type_name, .fields=fndef->args);
prebind_statement(env, args_def);
@ -635,9 +638,9 @@ CORD compile_statement(env_t *env, ast_t *ast)
all_args = CORD_all(all_args, "$", arg->name, arg->next ? ", " : CORD_EMPTY);
CORD pop_code = CORD_EMPTY;
if (fndef->cache->tag == Int && Match(fndef->cache, Int)->i < INT64_MAX) {
if (fndef->cache->tag == Int && cache_size < INT64_MAX) {
pop_code = CORD_all("if (Table$length(cache) > ", compile(body_scope, fndef->cache),
") Table$remove(&cache, NULL, table_type);\n");
") Table$remove(&cache, cache.entries.data + cache.entries.stride*Int$random(0, cache.entries.length-1), table_type);\n");
}
CORD arg_typedef = compile_struct_typedef(env, args_def);
@ -1961,6 +1964,10 @@ CORD compile(env_t *env, ast_t *ast)
CORD self = compile_to_pointer_depth(env, call->self, 1, false);
(void)compile_arguments(env, ast, NULL, call->args);
return CORD_all("Table$clear(", self, ")");
} else if (streq(call->name, "sorted")) {
CORD self = compile_to_pointer_depth(env, call->self, 0, false);
(void)compile_arguments(env, ast, NULL, call->args);
return CORD_all("Table$sorted(", self, ", ", compile_type_info(env, self_value_t), ")");
} else code_err(ast, "There is no '%s' method for tables", call->name);
}
default: {

View File

@ -685,6 +685,7 @@ type_t *get_type(env_t *env, ast_t *ast)
else if (streq(call->name, "set")) return Type(VoidType);
else if (streq(call->name, "remove")) return Type(VoidType);
else if (streq(call->name, "clear")) return Type(VoidType);
else if (streq(call->name, "sorted")) return self_value_t;
else code_err(ast, "There is no '%s' method for tables", call->name);
}
default: {