first pass at unified evaluation cache

This commit is contained in:
Ryan Fleury
2025-04-23 17:29:31 -07:00
parent 23125dd312
commit 2f7e48e5cf
6 changed files with 426 additions and 0 deletions
+5
View File
@@ -4819,6 +4819,11 @@ ctrl_thread__eval_scope_begin(Arena *arena, CTRL_Entity *thread)
}
e_select_interpret_ctx(&scope->interpret_ctx, eval_modules_primary->rdi, thread_rip_voff);
////////////////////////////
//- rjf: begin cached evaluations
//
e_cache_eval_begin();
return scope;
}
+256
View File
@@ -0,0 +1,256 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Basic Key Helpers
internal B32
e_key_match(E_Key a, E_Key b)
{
B32 result = (a.u64 == b.u64);
return result;
}
////////////////////////////////
//~ rjf: Cache Initialization (Required For All Subsequent APIs)
internal void
e_cache_eval_begin(void)
{
if(e_cache == 0)
{
Arena *arena = arena_alloc();
e_cache = push_array(arena, E_Cache, 1);
e_cache->arena = arena;
e_cache->arena_eval_start_pos = arena_pos(arena);
}
arena_pop_to(e_cache->arena, e_cache->arena_eval_start_pos);
e_cache->key_id_gen = 0;
e_cache->key_slots_count = 4096;
e_cache->key_slots = push_array(e_cache->arena, E_CacheSlot, e_cache->key_slots_count);
e_cache->string_slots_count = 4096;
e_cache->string_slots = push_array(e_cache->arena, E_CacheSlot, e_cache->string_slots_count);
e_cache->free_parent_node = 0;
e_cache->top_parent_node = 0;
}
////////////////////////////////
//~ rjf: Cache Accessing Functions
//- rjf: parent key stack
internal E_Key
e_parent_key_push(E_Key key)
{
E_Key top = {0};
if(e_cache->top_parent_node != 0)
{
top = e_cache->top_parent_node->key;
}
E_CacheParentNode *n = e_cache->free_parent_node;
if(n != 0)
{
SLLStackPop(e_cache->free_parent_node);
}
else
{
n = push_array(e_cache->arena, E_CacheParentNode, 1);
}
SLLStackPush(e_cache->top_parent_node, n);
n->key = key;
return top;
}
internal E_Key
e_parent_key_pop(void)
{
E_CacheParentNode *n = e_cache->top_parent_node;
SLLStackPop(e_cache->top_parent_node);
SLLStackPush(e_cache->free_parent_node, n);
E_Key popped = n->key;
return popped;
}
//- rjf: key construction
internal E_Key
e_key_from_string(String8 string)
{
E_Key parent_key = {0};
if(e_cache->top_parent_node)
{
parent_key = e_cache->top_parent_node->key;
}
U64 hash = e_hash_from_string(parent_key.u64, string);
U64 slot_idx = hash%e_cache->string_slots_count;
E_CacheSlot *slot = &e_cache->string_slots[slot_idx];
E_CacheNode *node = 0;
for(E_CacheNode *n = slot->first; n != 0; n = n->string_next)
{
if(e_key_match(parent_key, n->bundle.parent_key) && str8_match(n->bundle.string, string, 0))
{
node = n;
break;
}
}
if(node == 0)
{
e_cache->key_id_gen += 1;
E_Key key = {e_cache->key_id_gen};
U64 key_hash = e_hash_from_string(5381, str8_struct(&key));
U64 key_slot_idx = key_hash%e_cache->key_slots_count;
E_CacheSlot *key_slot = &e_cache->key_slots[key_slot_idx];
node = push_array(e_cache->arena, E_CacheNode, 1);
node->key = key;
SLLQueuePush_N(slot->first, slot->last, node, string_next);
SLLQueuePush_N(key_slot->first, key_slot->last, node, key_next);
node->bundle.parent_key = parent_key;
node->bundle.string = push_str8_copy(e_cache->arena, string);
}
return node->key;
}
internal E_Key
e_key_from_stringf(char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
E_Key result = e_key_from_string(string);
va_end(args);
scratch_end(scratch);
return result;
}
//- rjf: base key -> node helper
internal E_CacheBundle *
e_cache_bundle_from_key(E_Key key)
{
U64 hash = e_hash_from_string(5381, str8_struct(&key));
U64 slot_idx = hash%e_cache->key_slots_count;
E_CacheSlot *slot = &e_cache->key_slots[slot_idx];
E_CacheNode *node = 0;
for(E_CacheNode *n = slot->first; n != 0; n = n->key_next)
{
if(e_key_match(n->key, key))
{
node = n;
break;
}
}
E_CacheBundle *bundle = &e_cache_bundle_nil;
if(node != 0)
{
bundle = &node->bundle;
}
return bundle;
}
//- rjf: bundle -> pipeline stage outputs
internal E_Parse
e_parse_from_bundle(E_CacheBundle *bundle)
{
if(bundle != &e_cache_bundle_nil && !(bundle->flags & E_CacheBundleFlag_Parse))
{
bundle->flags |= E_CacheBundleFlag_Parse;
bundle->parse = e_push_parse_from_string(e_cache->arena, bundle->string);
}
E_Parse parse = bundle->parse;
return parse;
}
internal E_IRTreeAndType
e_irtree_from_bundle(E_CacheBundle *bundle)
{
if(bundle != &e_cache_bundle_nil && !(bundle->flags & E_CacheBundleFlag_IRTree))
{
bundle->flags |= E_CacheBundleFlag_IRTree;
E_IRTreeAndType overridden = e_irtree_from_key(bundle->parent_key);
E_IRTreeAndType *prev_overridden = e_ir_state->overridden_irtree;
e_ir_state->overridden_irtree = &overridden;
E_Parse parse = e_parse_from_bundle(bundle);
bundle->irtree = e_push_irtree_and_type_from_expr(e_cache->arena, parse.expr);
e_ir_state->overridden_irtree = prev_overridden;
}
E_IRTreeAndType result = bundle->irtree;
return result;
}
internal String8
e_bytecode_from_bundle(E_CacheBundle *bundle)
{
if(bundle != &e_cache_bundle_nil && !(bundle->flags & E_CacheBundleFlag_Bytecode))
{
bundle->flags |= E_CacheBundleFlag_Bytecode;
Temp scratch = scratch_begin(0, 0);
E_IRTreeAndType irtree = e_irtree_from_bundle(bundle);
E_OpList oplist = e_oplist_from_irtree(scratch.arena, irtree.root);
bundle->bytecode = e_bytecode_from_oplist(e_cache->arena, &oplist);
scratch_end(scratch);
}
String8 result = bundle->bytecode;
return result;
}
internal E_Interpretation
e_interpretation_from_bundle(E_CacheBundle *bundle)
{
if(bundle != &e_cache_bundle_nil && !(bundle->flags & E_CacheBundleFlag_Interpret))
{
bundle->flags |= E_CacheBundleFlag_Interpret;
String8 bytecode = e_bytecode_from_bundle(bundle);
E_Interpretation interpret = e_interpret(bytecode);
bundle->interpretation = interpret;
}
E_Interpretation interpret = bundle->interpretation;
return interpret;
}
//- rjf: comprehensive bundle
internal E_Eval
e_eval_from_bundle(E_CacheBundle *bundle)
{
E_Eval eval =
{
.expr = e_parse_from_bundle(bundle).expr,
.irtree = e_irtree_from_bundle(bundle),
.bytecode = e_bytecode_from_bundle(bundle),
};
E_Interpretation interpretation = e_interpretation_from_bundle(bundle);
eval.code = interpretation.code;
eval.value = interpretation.value;
eval.space = interpretation.space;
return eval;
}
//- rjf: string-based helpers
// TODO(rjf): (replace the old bundle APIs here)
////////////////////////////////
//~ rjf: Key Extension Functions
internal E_Key
e_key_wrap(E_Key key, String8 string)
{
e_parent_key_push(key);
E_Key result = e_key_from_string(string);
e_parent_key_pop();
return result;
}
internal E_Key
e_key_wrapf(E_Key key, char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
E_Key result = e_key_wrap(key, string);
va_end(args);
scratch_end(scratch);
return result;
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef EVAL_CACHE_H
#define EVAL_CACHE_H
////////////////////////////////
//~ rjf: Cache Key Type
typedef struct E_Key E_Key;
struct E_Key
{
U64 u64;
};
////////////////////////////////
//~ rjf: Cache Types
typedef U32 E_CacheBundleFlags;
enum
{
E_CacheBundleFlag_Parse = (1<<0),
E_CacheBundleFlag_IRTree = (1<<1),
E_CacheBundleFlag_Bytecode = (1<<2),
E_CacheBundleFlag_Interpret = (1<<3),
};
typedef struct E_CacheBundle E_CacheBundle;
struct E_CacheBundle
{
E_CacheBundleFlags flags;
E_Key parent_key;
String8 string;
E_Parse parse;
E_IRTreeAndType irtree;
String8 bytecode;
E_Interpretation interpretation;
};
typedef struct E_CacheNode E_CacheNode;
struct E_CacheNode
{
E_CacheNode *string_next;
E_CacheNode *key_next;
E_Key key;
E_CacheBundle bundle;
};
typedef struct E_CacheLookup E_CacheLookup;
struct E_CacheLookup
{
E_CacheNode *node;
U64 hash;
};
typedef struct E_CacheSlot E_CacheSlot;
struct E_CacheSlot
{
E_CacheNode *first;
E_CacheNode *last;
};
typedef struct E_CacheParentNode E_CacheParentNode;
struct E_CacheParentNode
{
E_CacheParentNode *next;
E_Key key;
};
typedef struct E_Cache E_Cache;
struct E_Cache
{
Arena *arena;
U64 arena_eval_start_pos;
U64 key_id_gen;
U64 key_slots_count;
E_CacheSlot *key_slots;
U64 string_slots_count;
E_CacheSlot *string_slots;
E_CacheParentNode *top_parent_node;
E_CacheParentNode *free_parent_node;
};
////////////////////////////////
//~ rjf: Globals
thread_static E_Cache *e_cache = 0;
read_only global E_CacheBundle e_cache_bundle_nil = {0, {0}, {0}, {{0}, 0, &e_expr_nil, &e_expr_nil}, {&e_irnode_nil}};
////////////////////////////////
//~ rjf: Basic Key Helpers
internal B32 e_key_match(E_Key a, E_Key b);
////////////////////////////////
//~ rjf: Cache Initialization (Required For All Subsequent APIs)
internal void e_cache_eval_begin(void);
////////////////////////////////
//~ rjf: Cache Accessing Functions
//
// The cache uses a unique keying mechanism to refer to some evaluation at
// many layers of analysis.
//
// key
// ________________________________________________
// / / | \
// text -> expression -> ir tree and type -> interpretation result
//
// Each one of these calls refers to one stage in this pipeline. The cache will
// only compute what is needed on-demand. If you ask for the full evaluation,
// which is a bundle of artifacts at all layers of analysis, then all stages
// will be computed.
//
// One wrinkle here is that the IR tree generation stage is implicitly
// parameterized by the "overridden" IR tree - this is to enable "parent
// expressions", e.g. `$.x`, or simply `x` assuming `foo` has such a member,
// in the context of some struct `foo` evaluates to the same thing as `foo.x`.
// So even though the primary API shape is based around singular keys, the
// "parent key stack" also implicitly parameterizes all of these (partly
// because it is not relevant in 99% of cases).
//- rjf: parent key stack
internal E_Key e_parent_key_push(E_Key key);
internal E_Key e_parent_key_pop(void);
//- rjf: key construction
internal E_Key e_key_from_string(String8 string);
internal E_Key e_key_from_stringf(char *fmt, ...);
//- rjf: base key -> bundle helper
internal E_CacheBundle *e_cache_bundle_from_key(E_Key key);
//- rjf: bundle -> pipeline stage outputs
internal E_Parse e_parse_from_bundle(E_CacheBundle *bundle);
internal E_IRTreeAndType e_irtree_from_bundle(E_CacheBundle *bundle);
internal String8 e_bytecode_from_bundle(E_CacheBundle *bundle);
internal E_Interpretation e_interpretation_from_bundle(E_CacheBundle *bundle);
#define e_parse_from_key(key) e_parse_from_bundle(e_cache_bundle_from_key(key))
#define e_irtree_from_key(key) e_irtree_from_bundle(e_cache_bundle_from_key(key))
#define e_bytecode_from_key(key) e_bytecode_from_bundle(e_cache_bundle_from_key(key))
#define e_interpretation_from_key(key) e_interpretation_from_bundle(e_cache_bundle_from_key(key))
//- rjf: comprehensive bundle
internal E_Eval e_eval_from_bundle(E_CacheBundle *bundle);
#define e_eval_from_key(key) e_eval_from_bundle(e_cache_bundle_from_key(key))
//- rjf: string-based helpers
// TODO(rjf): (replace the old bundle APIs here)
////////////////////////////////
//~ rjf: Key Extension Functions
internal E_Key e_key_wrap(E_Key key, String8 string);
internal E_Key e_key_wrapf(E_Key key, char *fmt, ...);
#endif // EVAL_CACHE_H
+1
View File
@@ -7,3 +7,4 @@
#include "eval/eval_ir.c"
#include "eval/eval_interpret.c"
#include "eval/eval_bundles.c"
#include "eval/eval_cache.c"
+1
View File
@@ -10,5 +10,6 @@
#include "eval/eval_ir.h"
#include "eval/eval_interpret.h"
#include "eval/eval_bundles.h"
#include "eval/eval_cache.h"
#endif // EVAL_INC_H
+5
View File
@@ -12460,6 +12460,11 @@ rd_frame(void)
}
e_select_interpret_ctx(interpret_ctx, eval_modules_primary->rdi, rip_voff);
////////////////////////////
//- rjf: begin cached evaluations
//
e_cache_eval_begin();
////////////////////////////
//- rjf: autosave if needed
//