Lime Parser Generator 0.1.0
Runtime-extensible LALR(1) parser with SIMD tokenization and LLVM JIT
Loading...
Searching...
No Matches
jit_context.h
1/*
2** LLVM OrcJIT compilation context for the extensible SQL parser.
3**
4** Provides runtime compilation of parser hot paths (action table lookups)
5** using LLVM's OrcJIT (LLJIT) infrastructure. The JIT compiles a
6** monolithic parse function that processes entire token sequences with
7** fully-inlined state dispatch, eliminating per-token function call
8** overhead.
9**
10** OrcJIT replaces the deprecated MCJIT engine and provides:
11** - Thread-safe contexts for concurrent compilation
12** - Lazy compilation support (for future tiered compilation)
13** - Better resource management and error handling via LLVMErrorRef
14**
15** When LLVM is not available at compile time (LIME_NO_JIT is defined),
16** all JIT functions degrade to no-ops and the parser falls back to the
17** standard interpreted table-driven approach.
18*/
19#ifndef JIT_CONTEXT_H
20#define JIT_CONTEXT_H
21
22#include <stdint.h>
23#include <stdbool.h>
24
25#ifdef __cplusplus
26extern "C" {
27#endif
28
29/* Forward declaration */
30typedef struct ParserSnapshot ParserSnapshot;
31
32/* ------------------------------------------------------------------ */
33/* JIT compilation status codes */
34/* ------------------------------------------------------------------ */
35
36typedef enum JITStatus {
37 JIT_OK = 0, /* Operation succeeded */
38 JIT_ERR_NO_LLVM, /* LLVM not available (compiled without) */
39 JIT_ERR_INIT_FAILED, /* LLVM initialization failed */
40 JIT_ERR_CODEGEN_FAILED, /* Code generation failed */
41 JIT_ERR_COMPILE_FAILED, /* JIT compilation failed */
42 JIT_ERR_LOOKUP_FAILED, /* Symbol lookup in JIT module failed */
43 JIT_ERR_INVALID_ARG, /* NULL or invalid argument */
44 JIT_ERR_ALREADY_COMPILED /* Snapshot already has JIT code */
45} JITStatus;
46
47/* ------------------------------------------------------------------ */
48/* JIT statistics */
49/* ------------------------------------------------------------------ */
50
54typedef struct JITStats {
55 uint32_t states_compiled;
56 uint32_t states_total;
57 uint64_t compile_time_ns;
58 uint64_t code_size_bytes;
59 bool available;
60 bool skip_opts;
64} JITStats;
65
66/* ------------------------------------------------------------------ */
67/* Opaque JIT context handle */
68/* ------------------------------------------------------------------ */
69
70/*
71** The JITContext is an opaque handle that holds LLVM state (module,
72** execution engine, compiled function pointers). It is stored in
73** ParserSnapshot.jit_ctx and freed when the snapshot is destroyed.
74*/
75typedef struct JITContext JITContext;
76
77/*
78** Function pointer type for JIT-compiled shift action lookup.
79**
80** Given a lookahead token code, returns the action code (shift, reduce,
81** or error). Each compiled state has its own function with the action
82** table logic baked into the instruction stream.
83*/
84typedef uint16_t (*JITShiftActionFn)(uint16_t iLookAhead);
85
86/*
87** Per-token shift-action lookup, parameterised by both the parser
88** state and the lookahead. This is the function the runtime push
89** parser (parse_engine_step) calls per token when a snapshot has
90** JIT code attached -- it replaces the table-driven
91** find_shift_action() with a fully-inlined nested switch baked into
92** native code by LLVM.
93**
94** Returns the action code as a 32-bit value the engine masks down
95** to uint16_t for use. The 32-bit signature is load-bearing: the
96** aarch64 / x86_64 C ABIs require callers to zero-extend small
97** return types, but LLVM's `i16` return puts only the low 16 bits
98** in the return register, leaving the upper bits undefined. Using
99** i32 throughout lets the C side read a well-defined value via
100** uint32_t.
101*/
102typedef uint32_t (*JITFindShiftActionFn)(uint32_t state, uint32_t lookahead);
103
104/* ------------------------------------------------------------------ */
105/* Public API */
106/* ------------------------------------------------------------------ */
107
108/*
109** Create a new JIT context. Returns JIT_OK on success or an error
110** code if LLVM is unavailable or initialization fails.
111**
112** The context is heap-allocated and must be freed with jit_destroy().
113** On success *ctx_out is set to the new context; on failure it is NULL.
114*/
115JITStatus jit_create(JITContext **ctx_out);
116
117/*
118** Destroy a JIT context and free all associated LLVM resources.
119** Passing NULL is safe and does nothing.
120*/
121void jit_destroy(JITContext *ctx);
122
123/*
124** Compile JIT code for a parser snapshot's action tables.
125**
126** Generates optimized machine code for find_shift_action across all
127** parser states. On success the compiled function pointers are stored
128** inside the JITContext and can be queried with jit_get_shift_action().
129**
130** The snapshot must have valid action tables (yy_action, yy_lookahead,
131** yy_shift_ofst, yy_default arrays and nstate/action_count set).
132**
133** Returns JIT_OK on success, or an error code on failure. On failure
134** the context remains valid but contains no compiled code.
135*/
136JITStatus jit_compile_snapshot(JITContext *ctx, const ParserSnapshot *snap);
137
138/*
139** Look up the JIT-compiled shift action function for a given state.
140**
141** Returns the function pointer if state_id has been compiled, or NULL
142** if the state has no JIT code (caller should fall back to the
143** table-driven path).
144*/
145JITShiftActionFn jit_get_shift_action(const JITContext *ctx, uint32_t state_id);
146
147/*
148** Look up the JIT-compiled per-token shift-action function.
149**
150** Unlike jit_get_shift_action() (which returned a per-state function
151** in the original codegen design), this returns ONE function that
152** takes both `state` and `lookahead` arguments and returns the
153** action code. parse_engine_step() calls this per token when a
154** snapshot has JIT code attached, replacing the table-driven path.
155**
156** Returns the function pointer when jit_compile_snapshot() has run
157** successfully, NULL otherwise.
158*/
159JITFindShiftActionFn jit_get_find_shift_action(const JITContext *ctx);
160
161/*
162** Pre-warm the JIT for a set of hot parser states.
163**
164** Records which states are frequently visited so that future tiered
165** compilation can apply extra optimization to those states. Currently
166** the monolithic JIT function covers all states equally, so this is
167** a no-op beyond bookkeeping, but the API is provided for forward
168** compatibility.
169**
170** Parameters:
171** ctx - JIT context (must have compiled a snapshot)
172** hot_states - Array of state IDs to mark as hot
173** n - Number of entries in hot_states
174**
175** Returns JIT_OK on success, JIT_ERR_INVALID_ARG if ctx is NULL,
176** or JIT_ERR_NO_LLVM if compiled without LLVM.
177*/
178JITStatus jit_warmup(JITContext *ctx, const uint32_t *hot_states, uint32_t n);
179
180/*
181** Get JIT compilation statistics.
182*/
183JITStats jit_get_stats(const JITContext *ctx);
184
185/*
186** Return a human-readable string for a JIT status code.
187** The returned pointer is to static storage and must not be freed.
188*/
189const char *jit_status_string(JITStatus status);
190
191/*
192** Check whether JIT compilation is available at runtime.
193** Returns true if LLVM support was compiled in and initialization
194** succeeds, false otherwise.
195*/
196bool jit_is_available(void);
197
198/* ------------------------------------------------------------------ */
199/* Snapshot integration helpers */
200/* ------------------------------------------------------------------ */
201
202/*
203** Compile and attach JIT code to a snapshot.
204**
205** Convenience function that creates a JIT context, compiles code for
206** the snapshot's action tables, and stores the context in snap->jit_ctx.
207** If the snapshot already has a JIT context this is a no-op returning
208** JIT_ERR_ALREADY_COMPILED.
209**
210** Returns JIT_OK on success. On failure snap->jit_ctx is left unchanged.
211*/
212JITStatus jit_attach_to_snapshot(ParserSnapshot *snap);
213
214/*
215** Detach and destroy the JIT context from a snapshot.
216** This is called automatically by snapshot_release() when the refcount
217** reaches zero, but can also be called manually to free JIT resources
218** earlier.
219*/
220/*
221** LIME_WEAK -- portability shim for the weak-symbol mechanism.
222** On gcc/clang this expands to __attribute__((weak)). On MSVC
223** there is no equivalent; the macro expands to nothing and the
224** caller must guard the dispatch differently (the current call
225** site in src/snapshot.c does an `if (jit_detach_from_snapshot
226** != NULL)` check, which on MSVC always evaluates true since
227** the strong symbol is the only one available -- but that's
228** fine because on MSVC we don't ship the snapshot.c-into-.so
229** path that the weak symbol was protecting; lime_snapshot_create
230** on Windows links snapshot.c directly into the .dll along
231** with everything else).
232*/
233/* `__attribute__((weak))` on a declaration causes clang targeting
234** the MSVC ABI (x86_64-pc-windows-msvc) to emit a weak alias
235** definition in EVERY translation unit that pulls the header.
236** lld-link then errors with 'duplicate symbol: ...' because
237** the strong definition in jit_context.c collides with the
238** synthesised weak def in snapshot.c.obj. GNU ld dedupes; lld-link
239** does not. Make LIME_WEAK a no-op on Windows -- the runtime
240** doesn't have the dynamic-snapshot .so case there anyway. */
241#if (defined(__GNUC__) || defined(__clang__)) && (!defined(_WIN32) || defined(__MINGW32__))
242#define LIME_WEAK __attribute__((weak))
243#else
244#define LIME_WEAK
245#endif
246
247/*
248** Detach and dispose any JIT context attached to this snapshot.
249**
250** Declared as weak (where the compiler supports it) so that
251** snapshot.c -- which is bundled into the dynamically-built .so
252** produced by lime_snapshot_create -- can be linked without the
253** JIT library. When the JIT library *is* linked (the production
254** case), the strong definition in jit_context.c wins and the
255** call dispatches normally. When it's NOT (the .so-build case
256** on platforms that have weak symbols), the symbol resolves to
257** NULL at link time and snapshot.c skips the call -- safe
258** because a snapshot built that way never has jit_ctx set anyway.
259**
260** On MSVC the weak attribute is unavailable; the .so-build path
261** is also unavailable on Windows in v0.2.x (no equivalent of
262** dlopen for runtime-built .dlls), so the lack of weakness is
263** moot there.
264*/
265LIME_WEAK void jit_detach_from_snapshot(ParserSnapshot *snap);
266
267/*
268** Runtime dispatch: look up the shift action for a state+lookahead pair.
269**
270** If the snapshot has JIT code for the given state, uses the compiled
271** path. Otherwise falls back to the table-driven lookup using the
272** snapshot's action table arrays.
273**
274** This is the primary entry point for the parser runtime to query
275** shift actions when JIT is enabled.
276*/
277uint16_t jit_find_shift_action(const ParserSnapshot *snap, uint16_t stateno, uint16_t iLookAhead);
278
279/*
280** Batch parse a sequence of tokens using the JIT-compiled monolithic function.
281**
282** Processes all tokens in one call to avoid per-token function call overhead.
283** If JIT is available, calls the compiled jit_parse_sequence function.
284** Otherwise, falls back to calling jit_find_shift_action in a loop.
285**
286** Parameters:
287** snap - Parser snapshot (may or may not have JIT compiled)
288** tokens - Array of lookahead tokens to process
289** count - Number of tokens in the array
290** state_inout - Pointer to current parser state (updated after processing)
291*/
292void jit_parse_batch(const ParserSnapshot *snap, uint16_t *tokens, uint32_t count,
293 uint16_t *state_inout);
294
295#ifdef __cplusplus
296}
297#endif
298
299#endif /* JIT_CONTEXT_H */
JIT compilation statistics for a snapshot.
Definition jit_context.h:54
uint64_t code_size_bytes
Approximate generated code size in bytes.
Definition jit_context.h:58
bool available
True if JIT support is available at runtime.
Definition jit_context.h:59
bool skip_opts
True if codegen elected to skip optimisation passes (very large grammars where O2 does not scale).
Definition jit_context.h:60
uint64_t compile_time_ns
Wall-clock nanoseconds spent compiling.
Definition jit_context.h:57
uint32_t states_total
Total number of states in the snapshot.
Definition jit_context.h:56
uint32_t states_compiled
Number of states with JIT code attached.
Definition jit_context.h:55
Opaque snapshot handle.
Definition snapshot.h:177