forked from nextgres/uplpgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupl_cache.c
More file actions
204 lines (183 loc) · 6.48 KB
/
Copy pathupl_cache.c
File metadata and controls
204 lines (183 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*-------------------------------------------------------------------------
*
* upl_cache.c
* Compiled function cache — hash table keyed by function OID.
*
* This cache is language-agnostic. It stores compiled function
* pointers with invalidation based on fn_xmin/fn_tid (pg_proc row
* version) and a driver-supplied staleness check callback.
*
* Three cache states per entry:
* - Not present (MISS): function not yet seen → evaluate heuristic,
* then either compile and store, or store skip marker
* - jit_func == skip sentinel (SKIP): heuristic decided JIT
* wouldn't help → use interpreter on all future calls
* - jit_func points to a real UPL_func (HIT): use JIT'd code
*
* Cache invalidation:
* Each entry stores fn_xmin/fn_tid from the pg_proc row at the
* time of compilation, plus an opaque lang_func pointer. On
* lookup, fn_xmin/fn_tid are compared against the current values.
* The driver-supplied check_fn callback compares lang_func pointers
* to detect when the language compiler recreated its function struct.
*
* Memory management:
* The hash table lives in TopMemoryContext and persists for the
* lifetime of the backend. UPL_func entries are also in
* TopMemoryContext. Old JIT'd code is intentionally leaked when
* cache entries are invalidated (LLJIT doesn't support cheap
* per-function removal).
*
*
* Copyright (c) 2003-2014, Jonah H. Harris <[email protected]>
* Copyright (c) 2014-2026, NEXTGRES, LLC. <[email protected]>
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain a
* copy of the License in LICENSE or at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
*-------------------------------------------------------------------------
*/
#include "upl.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
/*
* Sentinel value stored in jit_func to indicate the heuristic decided
* not to JIT this function. Must be distinguishable from NULL (not cached)
* and from any valid pointer.
*/
static UPL_func upl_skip_jit_sentinel;
#define UPL_SKIP_JIT (&upl_skip_jit_sentinel)
typedef struct UPL_cache_entry
{
Oid fn_oid; /* hash key */
UPL_func *jit_func; /* NULL = not yet decided, SKIP, or real */
TransactionId fn_xmin; /* pg_proc row version for invalidation */
ItemPointerData fn_tid;
void *lang_func; /* opaque language-specific function struct */
} UPL_cache_entry;
static HTAB *upl_cache_htab = NULL;
/*
* upl_cache_init - Initialize the function cache hash table.
*/
void
upl_cache_init(void)
{
HASHCTL ctl;
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(UPL_cache_entry);
ctl.hcxt = TopMemoryContext;
upl_cache_htab = hash_create("upl function cache",
32,
&ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
}
/*
* upl_cache_lookup - Look up a compiled function in the cache.
*
* Parameters:
* fn_oid, fn_xmin, fn_tid: current function identity from pg_proc
* current_lang_func: current language-specific function struct pointer
* check_fn: callback to compare cached vs current lang_func (may be NULL
* if no lang_func staleness check is needed)
* jitfunc_out: set to the cached compiled function on HIT
*
* Returns:
* UPL_CACHE_MISS - not in cache, caller should evaluate heuristic
* UPL_CACHE_SKIP - heuristic said skip JIT, use interpreter
* UPL_CACHE_HIT - *jitfunc_out set to compiled function
*/
int
upl_cache_lookup(Oid fn_oid, TransactionId fn_xmin, ItemPointerData fn_tid,
void *current_lang_func, upl_cache_check_fn check_fn,
UPL_func **jitfunc_out)
{
UPL_cache_entry *entry;
bool found;
*jitfunc_out = NULL;
if (upl_cache_htab == NULL)
return UPL_CACHE_MISS;
entry = (UPL_cache_entry *) hash_search(upl_cache_htab,
&fn_oid,
HASH_FIND,
&found);
if (!found)
return UPL_CACHE_MISS;
/*
* Check if the function has been replaced (CREATE OR REPLACE).
* The language compiler updates fn_xmin/fn_tid when it recompiles
* the AST from a changed pg_proc row.
*
* Also check via the driver callback if the language compiler returned
* a different function struct. The JIT'd code has AST node pointers
* embedded as LLVM constants from the original struct. If the compiler
* recreated the struct, those embedded pointers are dangling.
*/
if (entry->fn_xmin != fn_xmin ||
!ItemPointerEquals(&entry->fn_tid, &fn_tid) ||
(check_fn && !check_fn(entry->lang_func, current_lang_func)))
{
elog(LOG, "upl: invalidating cache for function %u "
"(xmin %u -> %u, lang_func %p -> %p)",
fn_oid,
entry->fn_xmin, fn_xmin,
entry->lang_func, current_lang_func);
/*
* Remove the entry so the heuristic is re-evaluated.
* Old JIT'd code (if any) is intentionally leaked.
*/
hash_search(upl_cache_htab, &fn_oid, HASH_REMOVE, NULL);
return UPL_CACHE_MISS;
}
if (entry->jit_func == UPL_SKIP_JIT)
return UPL_CACHE_SKIP;
/*
* Unreachable today — store always writes either a real pointer or the
* SKIP sentinel — but a NULL here would otherwise be reported as a HIT and
* the caller would jump through it. Treat it as "not yet decided".
*/
if (entry->jit_func == NULL)
return UPL_CACHE_MISS;
*jitfunc_out = entry->jit_func;
return UPL_CACHE_HIT;
}
/*
* upl_cache_store - Store a compiled function in cache.
*/
void
upl_cache_store(Oid fn_oid, TransactionId fn_xmin, ItemPointerData fn_tid,
void *lang_func, UPL_func *jitfunc)
{
UPL_cache_entry *entry;
bool found;
if (upl_cache_htab == NULL)
upl_cache_init();
entry = (UPL_cache_entry *) hash_search(upl_cache_htab,
&fn_oid,
HASH_ENTER,
&found);
entry->jit_func = jitfunc;
entry->fn_xmin = fn_xmin;
entry->fn_tid = fn_tid;
entry->lang_func = lang_func;
}
/*
* upl_cache_store_skip - Record that this function should not be JIT'd.
*/
void
upl_cache_store_skip(Oid fn_oid, TransactionId fn_xmin, ItemPointerData fn_tid,
void *lang_func)
{
upl_cache_store(fn_oid, fn_xmin, fn_tid, lang_func, UPL_SKIP_JIT);
}