File Coverage

lib/XS/JIT/xs_jit.c
Criterion Covered Total %
statement 366 435 84.1
branch 141 254 55.5
condition n/a
subroutine n/a
pod n/a
total 507 689 73.5


line stmt bran cond sub pod time code
1             /*
2             * xs_jit.c - Core C implementation for XS::JIT
3             *
4             * This file provides the JIT compilation functionality that can be
5             * called directly from C (no Perl stack overhead) or via XS bindings.
6             */
7              
8             #include "xs_jit.h"
9             #include
10             #include
11             #include
12             #include
13             #include
14              
15             /* PERL_VERSION_LE/GE macros for compile-time version checks in this file
16             * These are needed because older Perls don't define them */
17             #ifndef PERL_VERSION_DECIMAL
18             # define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
19             #endif
20             #ifndef PERL_DECIMAL_VERSION
21             # define PERL_DECIMAL_VERSION \
22             PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
23             #endif
24             #ifndef PERL_VERSION_GE
25             # define PERL_VERSION_GE(r,v,s) \
26             (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
27             #endif
28             #ifndef PERL_VERSION_LE
29             # define PERL_VERSION_LE(r,v,s) \
30             (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
31             #endif
32              
33             /* Buffer growth size */
34             #define BUFFER_CHUNK 4096
35              
36             /* Maximum path length */
37             #define MAX_PATH_LEN 4096
38              
39             /* Dynamic string buffer */
40             typedef struct {
41             char *data;
42             size_t len;
43             size_t capacity;
44             } StrBuf;
45              
46 276           static void strbuf_init(StrBuf *buf) {
47 276           buf->data = NULL;
48 276           buf->len = 0;
49 276           buf->capacity = 0;
50 276           }
51              
52 0           static void strbuf_free(StrBuf *buf) {
53 0 0         if (buf->data) {
54 0           free(buf->data);
55 0           buf->data = NULL;
56             }
57 0           buf->len = 0;
58 0           buf->capacity = 0;
59 0           }
60              
61 26845           static int strbuf_ensure(StrBuf *buf, size_t needed) {
62 26845 100         if (buf->capacity >= needed) return 1;
63              
64 389 100         size_t new_cap = buf->capacity ? buf->capacity : BUFFER_CHUNK;
65 505 100         while (new_cap < needed) new_cap *= 2;
66              
67 389           char *new_data = realloc(buf->data, new_cap);
68 389 50         if (!new_data) return 0;
69              
70 389           buf->data = new_data;
71 389           buf->capacity = new_cap;
72 389           return 1;
73             }
74              
75 24525           static int strbuf_append(StrBuf *buf, const char *str) {
76 24525           size_t slen = strlen(str);
77 24525 50         if (!strbuf_ensure(buf, buf->len + slen + 1)) return 0;
78 24525           memcpy(buf->data + buf->len, str, slen + 1);
79 24525           buf->len += slen;
80 24525           return 1;
81             }
82              
83 2320           static int strbuf_appendf(StrBuf *buf, const char *fmt, ...) {
84             va_list args, args2;
85 2320           va_start(args, fmt);
86 2320           va_copy(args2, args);
87              
88 2320           int needed = vsnprintf(NULL, 0, fmt, args);
89 2320           va_end(args);
90              
91 2320 50         if (needed < 0) {
92 0           va_end(args2);
93 0           return 0;
94             }
95              
96 2320 50         if (!strbuf_ensure(buf, buf->len + needed + 1)) {
97 0           va_end(args2);
98 0           return 0;
99             }
100              
101 2320           vsnprintf(buf->data + buf->len, needed + 1, fmt, args2);
102 2320           va_end(args2);
103 2320           buf->len += needed;
104 2320           return 1;
105             }
106              
107             /* Convert module name to safe C identifier */
108 1920           static void safe_name(const char *name, char *out, size_t outlen) {
109 1920           size_t i, j = 0;
110 35688 100         for (i = 0; name[i] && j < outlen - 1; i++) {
    50          
111 33768           char c = name[i];
112 33768 100         if (c == ':') {
113 1941           out[j++] = '_';
114 1941 50         if (name[i+1] == ':') i++;
115 31827 100         } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
    50          
    100          
    100          
    50          
116 1559 100         (c >= '0' && c <= '9') || c == '_') {
    50          
117 31827           out[j++] = c;
118             } else {
119 0           out[j++] = '_';
120             }
121             }
122 1920           out[j] = '\0';
123 1920           }
124              
125             /* Simple MD5-like hash for cache key (uses Perl's Digest::MD5) */
126 11           static char* compute_cache_key(pTHX_ const char *code, const char *name) {
127 11           dSP;
128             SV *input;
129 11           char *result = NULL;
130              
131             /* Build input string: code + name + archname + perl version */
132 11           HV *config = get_hv("Config::Config", 0);
133 11 50         SV **archname_sv = config ? hv_fetch(config, "archname", 8, 0) : NULL;
134 11 50         const char *archname = (archname_sv && *archname_sv) ? SvPV_nolen(*archname_sv) : "unknown";
    50          
135              
136 11           input = newSVpvf("%s\n%s\n%s\n%s", code, name, archname, PERL_VERSION_STRING);
137              
138 11           ENTER;
139 11           SAVETMPS;
140              
141             /* require Digest::MD5 */
142 11           eval_pv("require Digest::MD5", G_DISCARD);
143 11 50         if (SvTRUE(ERRSV)) {
    50          
144 0           SvREFCNT_dec(input);
145 0 0         FREETMPS;
146 0           LEAVE;
147 0           return NULL;
148             }
149              
150 11 50         PUSHMARK(SP);
151 11 50         XPUSHs(input);
152 11           PUTBACK;
153              
154 11           int count = call_pv("Digest::MD5::md5_hex", G_SCALAR);
155 11           SPAGAIN;
156              
157 11 50         if (count == 1) {
158 11           SV *md5_sv = POPs;
159             STRLEN len;
160 11           const char *md5 = SvPV(md5_sv, len);
161 11           result = (char*)malloc(len + 1);
162 11 50         if (result) {
163 11           memcpy(result, md5, len);
164 11           result[len] = '\0';
165             }
166             }
167              
168 11           PUTBACK;
169 11 50         FREETMPS;
170 11           LEAVE;
171              
172 11           SvREFCNT_dec(input);
173 11           return result;
174             }
175              
176             /* Get cache directory path */
177 286           static const char* get_cache_dir(const char *cache_dir) {
178 286 50         return cache_dir ? cache_dir : "_CACHED_XS";
179             }
180              
181             /* Build path to cached .so file */
182 11           int xs_jit_cache_path(pTHX_ const char *code, const char *name,
183             const char *cache_dir, char *out_path, size_t out_len) {
184 11           char *key = compute_cache_key(aTHX_ code, name);
185 11 50         if (!key) return 0;
186              
187 11           HV *config = get_hv("Config::Config", 0);
188 11 50         SV **dlext_sv = config ? hv_fetch(config, "dlext", 5, 0) : NULL;
189 11 50         const char *dlext = (dlext_sv && *dlext_sv) ? SvPV_nolen(*dlext_sv) : "so";
    50          
190              
191 11           const char *dir = get_cache_dir(cache_dir);
192              
193             char safe[256];
194 11           safe_name(name, safe, sizeof(safe));
195              
196 11           snprintf(out_path, out_len, "%s/lib/auto/%s/%s.%s",
197             dir, safe, safe, dlext);
198              
199 11           free(key);
200 11           return 1;
201             }
202              
203             /* Check if cached version exists */
204 11           int xs_jit_is_cached(pTHX_ const char *code, const char *name,
205             const char *cache_dir) {
206             char path[MAX_PATH_LEN];
207 11 50         if (!xs_jit_cache_path(aTHX_ code, name, cache_dir, path, sizeof(path))) {
208 0           return 0;
209             }
210              
211             Stat_t st;
212 11           return PerlLIO_stat(path, &st) == 0;
213             }
214              
215             /* Generate XS wrapper/alias for a single function */
216 542           static int generate_wrapper(StrBuf *buf, const char *target,
217             const char *source, int has_varargs, int is_xs_native) {
218             char safe_target[256];
219 542           safe_name(target, safe_target, sizeof(safe_target));
220              
221 542 100         if (is_xs_native) {
222             /* For XS-native functions, just create an alias - no wrapper needed.
223             * The user function already has proper XS signature and handles
224             * dXSARGS, ST(), XSRETURN, etc. itself.
225             */
226 471           strbuf_appendf(buf, "\n/* Alias for XS-native %s -> %s */\n", source, target);
227 471           strbuf_appendf(buf, "#define XS_%s %s\n", safe_target, source);
228             } else {
229 71           strbuf_appendf(buf, "\n/* XS wrapper for %s -> %s */\n", source, target);
230 71           strbuf_appendf(buf, "XS_EUPXS(XS_%s) {\n", safe_target);
231 71           strbuf_append(buf, " dVAR; dXSARGS;\n");
232              
233 71 50         if (has_varargs) {
234 71           strbuf_append(buf, " if (items < 1)\n");
235 71           strbuf_appendf(buf, " croak_xs_usage(cv, \"self, ...\");\n");
236             }
237              
238 71           strbuf_append(buf, " {\n");
239 71           strbuf_append(buf, " SV *RETVAL;\n");
240 71           strbuf_append(buf, " I32* temp = PL_markstack_ptr++;\n");
241 71           strbuf_appendf(buf, " RETVAL = %s(ST(0));\n", source);
242 71           strbuf_append(buf, " PL_markstack_ptr = temp;\n");
243 71           strbuf_append(buf, " if (RETVAL) {\n");
244 71           strbuf_append(buf, " RETVAL = sv_2mortal(RETVAL);\n");
245 71           strbuf_append(buf, " } else {\n");
246 71           strbuf_append(buf, " RETVAL = &PL_sv_undef;\n");
247 71           strbuf_append(buf, " }\n");
248 71           strbuf_append(buf, " ST(0) = RETVAL;\n");
249 71           strbuf_append(buf, " }\n");
250 71           strbuf_append(buf, " XSRETURN(1);\n");
251 71           strbuf_append(buf, "}\n");
252             }
253              
254 542           return 1;
255             }
256              
257             /* Generate boot function */
258 276           static int generate_boot(StrBuf *buf, const char *module_name,
259             XS_JIT_Func *functions, int num_funcs) {
260             char safe_module[256];
261             int i;
262 276           safe_name(module_name, safe_module, sizeof(safe_module));
263              
264 276           strbuf_append(buf, "\n/* Boot function */\n");
265 276           strbuf_append(buf, "#ifdef __cplusplus\n");
266 276           strbuf_append(buf, "extern \"C\" {\n");
267 276           strbuf_append(buf, "#endif\n");
268 276           strbuf_appendf(buf, "XS_EXTERNAL(boot_%s);\n", safe_module);
269 276           strbuf_appendf(buf, "XS_EXTERNAL(boot_%s) {\n", safe_module);
270              
271             /* JIT: emit correct boot args for this Perl version */
272             #if PERL_VERSION_LE(5, 21, 5)
273             strbuf_append(buf, " dVAR; dXSARGS;\n");
274             #else
275 276           strbuf_append(buf, " dVAR; dXSBOOTARGSXSAPIVERCHK;\n");
276             #endif
277              
278             /* JIT: emit correct file declaration for this Perl version */
279             #if PERL_VERSION_LE(5, 8, 999)
280             strbuf_append(buf, " char* file = __FILE__;\n");
281             #else
282 276           strbuf_append(buf, " const char* file = __FILE__;\n");
283             #endif
284              
285 276           strbuf_append(buf, "\n");
286 276           strbuf_append(buf, " PERL_UNUSED_VAR(file);\n");
287 276           strbuf_append(buf, " PERL_UNUSED_VAR(cv);\n");
288 276           strbuf_append(buf, " PERL_UNUSED_VAR(items);\n");
289 276           strbuf_append(buf, "\n");
290              
291             /* JIT: emit version check only for older Perls */
292             #if PERL_VERSION_LE(5, 21, 5)
293             strbuf_append(buf, " XS_VERSION_BOOTCHECK;\n");
294             # ifdef XS_APIVERSION_BOOTCHECK
295             strbuf_append(buf, " XS_APIVERSION_BOOTCHECK;\n");
296             # endif
297             #endif
298 276           strbuf_append(buf, "\n");
299              
300             /* Register each function */
301 818 100         for (i = 0; i < num_funcs; i++) {
302 542 50         if (!functions[i].target) break;
303              
304             char safe_target[256];
305 542           safe_name(functions[i].target, safe_target, sizeof(safe_target));
306              
307 542           strbuf_appendf(buf, " newXS_deffile(\"%s\", XS_%s);\n",
308 542           functions[i].target, safe_target);
309             }
310              
311 276           strbuf_append(buf, "\n");
312              
313             /* JIT: emit correct boot epilog for this Perl version */
314             #if PERL_VERSION_LE(5, 21, 5)
315             # if PERL_VERSION_GE(5, 9, 0)
316             strbuf_append(buf, " if (PL_unitcheckav)\n");
317             strbuf_append(buf, " call_list(PL_scopestack_ix, PL_unitcheckav);\n");
318             # endif
319             strbuf_append(buf, " XSRETURN_YES;\n");
320             #else
321 276           strbuf_append(buf, " Perl_xs_boot_epilog(aTHX_ ax);\n");
322             #endif
323              
324 276           strbuf_append(buf, "}\n");
325 276           strbuf_append(buf, "#ifdef __cplusplus\n");
326 276           strbuf_append(buf, "}\n");
327 276           strbuf_append(buf, "#endif\n");
328              
329 276           return 1;
330             }
331              
332             /* Generate complete C code */
333 276           char* xs_jit_generate_code(pTHX_ const char *user_code,
334             const char *module_name,
335             XS_JIT_Func *functions,
336             int num_funcs) {
337             StrBuf buf;
338             int i;
339 276           strbuf_init(&buf);
340              
341             /* Standard headers */
342 276           strbuf_append(&buf, "/*\n");
343 276           strbuf_append(&buf, " * Generated by XS::JIT\n");
344 276           strbuf_append(&buf, " * Do not edit this file directly.\n");
345 276           strbuf_append(&buf, " */\n\n");
346              
347 276           strbuf_append(&buf, "#include \"EXTERN.h\"\n");
348 276           strbuf_append(&buf, "#include \"perl.h\"\n");
349 276           strbuf_append(&buf, "#include \"XSUB.h\"\n");
350 276           strbuf_append(&buf, "\n");
351              
352             /* PERL_STATIC_INLINE compatibility for Perl < 5.13.4 */
353 276           strbuf_append(&buf, "#ifndef PERL_STATIC_INLINE\n");
354 276           strbuf_append(&buf, "# define PERL_STATIC_INLINE static\n");
355 276           strbuf_append(&buf, "#endif\n");
356 276           strbuf_append(&buf, "\n");
357              
358             /* PERL_VERSION_GE macro for version checks */
359 276           strbuf_append(&buf, "#ifndef PERL_VERSION_GE\n");
360 276           strbuf_append(&buf, "# define PERL_VERSION_GE(r,v,s) \\\n");
361 276           strbuf_append(&buf, " (PERL_REVISION > (r) || (PERL_REVISION == (r) && \\\n");
362 276           strbuf_append(&buf, " (PERL_VERSION > (v) || (PERL_VERSION == (v) && PERL_SUBVERSION >= (s)))))\n");
363 276           strbuf_append(&buf, "#endif\n");
364 276           strbuf_append(&buf, "\n");
365              
366             /* XOP API backwards compatibility for pre-5.14 Perl */
367 276           strbuf_append(&buf, "#if !PERL_VERSION_GE(5,14,0)\n");
368 276           strbuf_append(&buf, "/* Pre-5.14: use deprecated custom op interface */\n");
369 276           strbuf_append(&buf, "# ifndef XOP_DEFINED_BY_COMPAT\n");
370 276           strbuf_append(&buf, "# define XOP_DEFINED_BY_COMPAT 1\n");
371 276           strbuf_append(&buf, "typedef struct { const char *xop_name; const char *xop_desc; U32 xop_class; } XOP;\n");
372 276           strbuf_append(&buf, "# endif\n");
373 276           strbuf_append(&buf, "# ifndef OA_UNOP\n");
374 276           strbuf_append(&buf, "# define OA_UNOP 1\n");
375 276           strbuf_append(&buf, "# endif\n");
376 276           strbuf_append(&buf, "# ifndef OA_BINOP\n");
377 276           strbuf_append(&buf, "# define OA_BINOP 2\n");
378 276           strbuf_append(&buf, "# endif\n");
379 276           strbuf_append(&buf, "# ifndef XopENTRY_set\n");
380 276           strbuf_append(&buf, "# define XopENTRY_set(xop, field, value) do { (xop)->field = (value); } while(0)\n");
381 276           strbuf_append(&buf, "# endif\n");
382             /* Real function (not a macro): the preprocessor identifies macro
383             arguments by commas at the call site BEFORE expanding aTHX_, so
384             a 3-arg macro can't be called with `Perl_custom_op_register(aTHX_ p, x)`
385             — that's two preprocessor args. Function form lets the C compiler
386             handle aTHX_ at the call site and matches the native 5.14+ ABI. */
387 276           strbuf_append(&buf, "PERL_STATIC_INLINE void Perl_custom_op_register(pTHX_ Perl_ppaddr_t ppfunc, const XOP *xop) {\n");
388 276           strbuf_append(&buf, " if (!PL_custom_op_names) PL_custom_op_names = newHV();\n");
389 276           strbuf_append(&buf, " if (!PL_custom_op_descs) PL_custom_op_descs = newHV();\n");
390 276           strbuf_append(&buf, " hv_store(PL_custom_op_names, (char*)&ppfunc, sizeof(ppfunc), newSVpv(xop->xop_name, 0), 0);\n");
391 276           strbuf_append(&buf, " hv_store(PL_custom_op_descs, (char*)&ppfunc, sizeof(ppfunc), newSVpv(xop->xop_desc, 0), 0);\n");
392 276           strbuf_append(&buf, "}\n");
393 276           strbuf_append(&buf, "#endif /* !PERL_VERSION_GE(5,14,0) */\n");
394 276           strbuf_append(&buf, "\n");
395              
396             /* Version compatibility macros - only emit if not defined on build system */
397             #ifndef PERL_UNUSED_VAR
398             strbuf_append(&buf, "#ifndef PERL_UNUSED_VAR\n");
399             strbuf_append(&buf, "# define PERL_UNUSED_VAR(var) if (0) var = var\n");
400             strbuf_append(&buf, "#endif\n");
401             strbuf_append(&buf, "\n");
402             #endif
403             #ifndef dVAR
404             strbuf_append(&buf, "#ifndef dVAR\n");
405             strbuf_append(&buf, "# define dVAR dNOOP\n");
406             strbuf_append(&buf, "#endif\n");
407             strbuf_append(&buf, "\n");
408             #endif
409              
410             /* PERL_VERSION macros - only emit if not defined on build system */
411             #ifndef PERL_VERSION_DECIMAL
412             strbuf_append(&buf, "#ifndef PERL_VERSION_DECIMAL\n");
413             strbuf_append(&buf, "# define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)\n");
414             strbuf_append(&buf, "#endif\n");
415             strbuf_append(&buf, "#ifndef PERL_DECIMAL_VERSION\n");
416             strbuf_append(&buf, "# define PERL_DECIMAL_VERSION \\\n");
417             strbuf_append(&buf, " PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)\n");
418             strbuf_append(&buf, "#endif\n");
419             strbuf_append(&buf, "#ifndef PERL_VERSION_GE\n");
420             strbuf_append(&buf, "# define PERL_VERSION_GE(r,v,s) \\\n");
421             strbuf_append(&buf, " (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))\n");
422             strbuf_append(&buf, "#endif\n");
423             strbuf_append(&buf, "#ifndef PERL_VERSION_LE\n");
424             strbuf_append(&buf, "# define PERL_VERSION_LE(r,v,s) \\\n");
425             strbuf_append(&buf, " (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))\n");
426             strbuf_append(&buf, "#endif\n");
427             strbuf_append(&buf, "\n");
428             #endif
429              
430             /* XS macros - only emit if not defined on build system */
431             #ifndef XS_EXTERNAL
432             strbuf_append(&buf, "#ifndef XS_EXTERNAL\n");
433             strbuf_append(&buf, "# define XS_EXTERNAL(name) XS(name)\n");
434             strbuf_append(&buf, "#endif\n");
435             strbuf_append(&buf, "#ifndef XS_INTERNAL\n");
436             strbuf_append(&buf, "# define XS_INTERNAL(name) XS(name)\n");
437             strbuf_append(&buf, "#endif\n");
438             #endif
439 276           strbuf_append(&buf, "#undef XS_EUPXS\n");
440 276           strbuf_append(&buf, "#if defined(PERL_EUPXS_ALWAYS_EXPORT)\n");
441 276           strbuf_append(&buf, "# define XS_EUPXS(name) XS_EXTERNAL(name)\n");
442 276           strbuf_append(&buf, "#else\n");
443 276           strbuf_append(&buf, "# define XS_EUPXS(name) XS_INTERNAL(name)\n");
444 276           strbuf_append(&buf, "#endif\n");
445 276           strbuf_append(&buf, "\n");
446              
447             /* newXS_deffile compatibility - JIT: emit only correct version */
448             #if PERL_VERSION_LE(5, 21, 5)
449             strbuf_append(&buf, "#define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)\n");
450             #else
451 276           strbuf_append(&buf, "#define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)\n");
452             #endif
453 276           strbuf_append(&buf, "\n");
454              
455             /* Op sibling compatibility for Perl < 5.22 (added in 5.21.2)
456             * Only emit compat code when XS::JIT is built on older Perl - true JIT behavior */
457             #ifndef OpHAS_SIBLING
458             strbuf_append(&buf, "/* Op sibling compatibility for Perl < 5.22 */\n");
459             strbuf_append(&buf, "#ifndef OpHAS_SIBLING\n");
460             strbuf_append(&buf, "# define OpHAS_SIBLING(o) ((o)->op_sibling != NULL)\n");
461             strbuf_append(&buf, "# define OpSIBLING(o) ((o)->op_sibling)\n");
462             strbuf_append(&buf, "# define OpMORESIB_set(o, sib) ((o)->op_sibling = (sib))\n");
463             strbuf_append(&buf, "# define OpLASTSIB_set(o, parent) ((o)->op_sibling = NULL)\n");
464             strbuf_append(&buf, "PERL_STATIC_INLINE OP*\n");
465             strbuf_append(&buf, "S_op_sibling_splice_compat(OP *parent, OP *start, int del_count, OP *insert)\n");
466             strbuf_append(&buf, "{\n");
467             strbuf_append(&buf, " OP *first, *rest, *last_del = NULL;\n");
468             strbuf_append(&buf, " if (!parent) return NULL;\n");
469             strbuf_append(&buf, " if (start) first = start->op_sibling;\n");
470             strbuf_append(&buf, " else first = cLISTOPx(parent)->op_first;\n");
471             strbuf_append(&buf, " rest = first;\n");
472             strbuf_append(&buf, " while (del_count && rest) {\n");
473             strbuf_append(&buf, " last_del = rest;\n");
474             strbuf_append(&buf, " rest = rest->op_sibling;\n");
475             strbuf_append(&buf, " del_count--;\n");
476             strbuf_append(&buf, " }\n");
477             strbuf_append(&buf, " if (last_del) last_del->op_sibling = NULL;\n");
478             strbuf_append(&buf, " if (insert) {\n");
479             strbuf_append(&buf, " OP *insert_last = insert;\n");
480             strbuf_append(&buf, " while (insert_last->op_sibling) insert_last = insert_last->op_sibling;\n");
481             strbuf_append(&buf, " insert_last->op_sibling = rest;\n");
482             strbuf_append(&buf, " if (start) start->op_sibling = insert;\n");
483             strbuf_append(&buf, " else cLISTOPx(parent)->op_first = insert;\n");
484             strbuf_append(&buf, " } else {\n");
485             strbuf_append(&buf, " if (start) start->op_sibling = rest;\n");
486             strbuf_append(&buf, " else cLISTOPx(parent)->op_first = rest;\n");
487             strbuf_append(&buf, " }\n");
488             strbuf_append(&buf, " return first;\n");
489             strbuf_append(&buf, "}\n");
490             strbuf_append(&buf, "# define op_sibling_splice(parent, start, del_count, insert) \\\n");
491             strbuf_append(&buf, " S_op_sibling_splice_compat(parent, start, del_count, insert)\n");
492             strbuf_append(&buf, "#endif\n");
493             strbuf_append(&buf, "\n");
494             #endif
495              
496             /* XS::JIT convenience macros */
497 276           strbuf_append(&buf, "/* XS::JIT convenience macros */\n");
498 276           strbuf_append(&buf, "#define JIT_ARGS dTHX; dXSARGS\n");
499 276           strbuf_append(&buf, "\n");
500              
501             /* Inline compatibility macros */
502 276           strbuf_append(&buf, "/* Inline::C compatibility macros */\n");
503 276           strbuf_append(&buf, "#define Inline_Stack_Vars dXSARGS\n");
504 276           strbuf_append(&buf, "#define Inline_Stack_Items items\n");
505 276           strbuf_append(&buf, "#define Inline_Stack_Item(x) ST(x)\n");
506 276           strbuf_append(&buf, "#define Inline_Stack_Reset sp = mark\n");
507 276           strbuf_append(&buf, "#define Inline_Stack_Push(x) XPUSHs(x)\n");
508 276           strbuf_append(&buf, "#define Inline_Stack_Done PUTBACK\n");
509 276           strbuf_append(&buf, "#define Inline_Stack_Return(x) XSRETURN(x)\n");
510 276           strbuf_append(&buf, "#define Inline_Stack_Void XSRETURN(0)\n");
511 276           strbuf_append(&buf, "\n");
512              
513             /* User code */
514 276           strbuf_append(&buf, "/* ========== User Code ========== */\n\n");
515 276           strbuf_append(&buf, user_code);
516 276           strbuf_append(&buf, "\n\n");
517 276           strbuf_append(&buf, "/* ========== XS Wrappers ========== */\n");
518              
519             /* Generate wrapper for each function */
520 818 100         for (i = 0; i < num_funcs; i++) {
521 542 50         if (!functions[i].target) break;
522 542           generate_wrapper(&buf, functions[i].target, functions[i].source,
523 542           functions[i].has_varargs, functions[i].is_xs_native);
524             }
525              
526             /* Generate boot function */
527 276           generate_boot(&buf, module_name, functions, num_funcs);
528              
529 276           return buf.data; /* Caller must free */
530             }
531              
532             /* Create directory recursively */
533 275           static int mkdir_p(const char *path) {
534             char tmp[MAX_PATH_LEN];
535 275           char *p = NULL;
536             size_t len;
537              
538 275           snprintf(tmp, sizeof(tmp), "%s", path);
539 275           len = strlen(tmp);
540 275 50         if (tmp[len - 1] == '/') tmp[len - 1] = 0;
541              
542 10807 100         for (p = tmp + 1; *p; p++) {
543 10532 100         if (*p == '/') {
544 1069           *p = 0;
545 1069           mkdir(tmp, 0755);
546 1069           *p = '/';
547             }
548             }
549 275 100         return mkdir(tmp, 0755) == 0 || errno == EEXIST;
    50          
550             }
551              
552             /* Run a shell command and capture its merged stdout+stderr (the caller
553             already appends "2>&1" so anything gcc/ld writes lands in the pipe).
554             Returns the command's exit status (0 on success); *out_sv receives a
555             mortal SV with the captured text, or NULL if popen itself failed. */
556 549           static int xs_jit_run_capture(pTHX_ const char *cmd, SV **out_sv) {
557             #if defined(WIN32)
558             FILE *fp = _popen(cmd, "r");
559             #else
560 549           FILE *fp = popen(cmd, "r");
561             #endif
562 549 50         if (!fp) {
563 0           *out_sv = NULL;
564 0           return -1;
565             }
566 549           SV *sv = newSVpvs("");
567             char buf[4096];
568             size_t n;
569 552 100         while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
570 3           sv_catpvn(sv, buf, n);
571             #if defined(WIN32)
572             int rc = _pclose(fp);
573             #else
574 549           int rc = pclose(fp);
575             #endif
576 549           *out_sv = sv_2mortal(sv);
577 549           return rc;
578             }
579              
580             /* Compile C file to shared object */
581 275           int xs_jit_compile_file(pTHX_ const char *c_file, const char *so_file,
582             const char *extra_cflags, const char *extra_ldflags) {
583 275           HV *config = get_hv("Config::Config", 0);
584 275 50         if (!config) {
585 0           warn("XS::JIT: Cannot access %%Config");
586 0           return 0;
587             }
588              
589 275           SV **cc_sv = hv_fetch(config, "cc", 2, 0);
590 275           SV **ccflags_sv = hv_fetch(config, "ccflags", 7, 0);
591 275           SV **optimize_sv = hv_fetch(config, "optimize", 8, 0);
592 275           SV **cccdlflags_sv = hv_fetch(config, "cccdlflags", 10, 0);
593 275           SV **lddlflags_sv = hv_fetch(config, "lddlflags", 9, 0);
594 275           SV **archlib_sv = hv_fetch(config, "archlib", 7, 0);
595             #if defined(WIN32) || defined(__CYGWIN__) || defined(__MSYS__)
596             SV **libperl_sv = hv_fetch(config, "libperl", 7, 0);
597             #endif
598              
599 275 50         const char *cc = (cc_sv && *cc_sv) ? SvPV_nolen(*cc_sv) : "cc";
    50          
600 275 50         const char *ccflags = (ccflags_sv && *ccflags_sv) ? SvPV_nolen(*ccflags_sv) : "";
    50          
601 275 50         const char *optimize = (optimize_sv && *optimize_sv) ? SvPV_nolen(*optimize_sv) : "-O2";
    50          
602 275 50         const char *cccdlflags = (cccdlflags_sv && *cccdlflags_sv) ? SvPV_nolen(*cccdlflags_sv) : "";
    50          
603 275 50         const char *lddlflags = (lddlflags_sv && *lddlflags_sv) ? SvPV_nolen(*lddlflags_sv) : "";
    50          
604 275 50         const char *archlib = (archlib_sv && *archlib_sv) ? SvPV_nolen(*archlib_sv) : "";
    50          
605              
606             /* Default to empty string if NULL */
607 275 50         if (!extra_cflags) extra_cflags = "";
608 275 50         if (!extra_ldflags) extra_ldflags = "";
609              
610             char o_file[MAX_PATH_LEN];
611 275           snprintf(o_file, sizeof(o_file), "%s.o", c_file);
612              
613             char cmd[MAX_PATH_LEN * 4]; /* Larger buffer for extra flags */
614             int ret;
615              
616             /* Use plain -I; -iwithsysroot only works when PERL_INC sits under the
617             active SDK root (system perl). homebrew/perlbrew installs do not,
618             so -iwithsysroot makes the compiler not find EXTERN.h. EUMM itself
619             uses plain -I in the same situation. */
620 275           const char *inc_flag = "-I";
621              
622             /* Ensure C99 mode for for-loop variable declarations; skip if ccflags already sets -std= */
623 275 50         const char *std_flag = (strstr(ccflags, "-std=") == NULL) ? "-std=gnu99" : "";
624              
625             /* Compile to object with optimization and extra cflags */
626 275           snprintf(cmd, sizeof(cmd), "%s %s %s %s %s %s -c -o \"%s\" %s \"%s/CORE\" \"%s\" 2>&1",
627             cc, ccflags, std_flag, optimize, cccdlflags, extra_cflags, o_file, inc_flag, archlib, c_file);
628              
629 275           SV *captured = NULL;
630 275           ret = xs_jit_run_capture(aTHX_ cmd, &captured);
631 275 100         if (ret != 0) {
632 1 50         warn("XS::JIT: Compilation failed: %s\n--- compiler output ---\n%s",
633             cmd, captured ? SvPV_nolen(captured) : "(no output captured)");
634 1           return 0;
635             }
636              
637             /* Link to shared object with extra ldflags.
638             Cygwin/MSYS need the same lib-resolve as native Win32: --shared
639             requires resolving all symbols at link time, so we link against
640             -lperl5XX derived from $Config{libperl} and ask the linker to
641             export everything so other XS modules can reference symbols. */
642             #if defined(WIN32) || defined(__CYGWIN__) || defined(__MSYS__)
643             char perl_lib_flag[64] = "";
644             if (libperl_sv && *libperl_sv) {
645             const char *libperl = SvPV_nolen(*libperl_sv);
646             /* Strip leading "lib" and trailing ".a"/".dll.a" to get e.g. "perl542" */
647             if (strncmp(libperl, "lib", 3) == 0) {
648             const char *start = libperl + 3;
649             const char *dot = strchr(start, '.');
650             size_t len = dot ? (size_t)(dot - start) : strlen(start);
651             if (len < sizeof(perl_lib_flag) - 3) {
652             snprintf(perl_lib_flag, sizeof(perl_lib_flag), "-l%.*s", (int)len, start);
653             }
654             }
655             }
656             snprintf(cmd, sizeof(cmd),
657             "%s %s %s -Wl,--export-all-symbols -o \"%s\" \"%s\" -L\"%s/CORE\" %s 2>&1",
658             cc, lddlflags, extra_ldflags, so_file, o_file, archlib, perl_lib_flag);
659             #else
660 274           snprintf(cmd, sizeof(cmd), "%s %s %s -o \"%s\" \"%s\" 2>&1",
661             cc, lddlflags, extra_ldflags, so_file, o_file);
662             #endif
663              
664 274           captured = NULL;
665 274           ret = xs_jit_run_capture(aTHX_ cmd, &captured);
666 274 50         if (ret != 0) {
667 0 0         warn("XS::JIT: Linking failed: %s\n--- linker output ---\n%s",
668             cmd, captured ? SvPV_nolen(captured) : "(no output captured)");
669 0           return 0;
670             }
671              
672             /* Clean up object file */
673 274           unlink(o_file);
674              
675 274           return 1;
676             }
677              
678             /* Load compiled module using DynaLoader */
679 274           int xs_jit_load(pTHX_ const char *module_name, const char *so_file) {
680 274           dSP;
681             char safe_module[256];
682 274           safe_name(module_name, safe_module, sizeof(safe_module));
683              
684             /* Resolve to absolute path - macOS hardened runtime rejects relative paths in dlopen */
685             char abs_so_file[MAX_PATH_LEN];
686             #ifdef WIN32
687             if (_fullpath(abs_so_file, so_file, MAX_PATH_LEN)) {
688             so_file = abs_so_file;
689             }
690             #else
691 274 100         if (so_file[0] != '/') {
692 30 50         if (realpath(so_file, abs_so_file)) {
693 30           so_file = abs_so_file;
694             }
695             }
696             #endif
697              
698 274           ENTER;
699 274           SAVETMPS;
700              
701             /* require DynaLoader */
702 274           eval_pv("require DynaLoader", G_DISCARD);
703 274 50         if (SvTRUE(ERRSV)) {
    50          
704 0 0         warn("XS::JIT: Cannot load DynaLoader: %s", SvPV_nolen(ERRSV));
705 0 0         FREETMPS;
706 0           LEAVE;
707 0           return 0;
708             }
709              
710             /* dl_load_file */
711 274 50         PUSHMARK(SP);
712 274 50         XPUSHs(sv_2mortal(newSVpv(so_file, 0)));
713 274 50         XPUSHs(sv_2mortal(newSViv(0))); /* flags */
714 274           PUTBACK;
715              
716 274           int count = call_pv("DynaLoader::dl_load_file", G_SCALAR);
717 274           SPAGAIN;
718              
719 274           SV *libref_sv = NULL;
720 274 50         if (count == 1) {
721 274           libref_sv = POPs;
722 274           SvREFCNT_inc(libref_sv);
723             }
724 274           PUTBACK;
725              
726 274 50         if (!libref_sv || !SvOK(libref_sv)) {
    50          
727             /* Get error */
728 0 0         PUSHMARK(SP);
729 0           PUTBACK;
730 0           call_pv("DynaLoader::dl_error", G_SCALAR);
731 0           SPAGAIN;
732 0           SV *err = POPs;
733 0           warn("XS::JIT: dl_load_file failed: %s", SvPV_nolen(err));
734 0           PUTBACK;
735 0 0         FREETMPS;
736 0           LEAVE;
737 0           return 0;
738             }
739              
740             /* dl_find_symbol for boot function */
741             char boot_name[300];
742 274           snprintf(boot_name, sizeof(boot_name), "boot_%s", safe_module);
743              
744 274 50         PUSHMARK(SP);
745 274 50         XPUSHs(libref_sv);
746 274 50         XPUSHs(sv_2mortal(newSVpv(boot_name, 0)));
747 274           PUTBACK;
748              
749 274           count = call_pv("DynaLoader::dl_find_symbol", G_SCALAR);
750 274           SPAGAIN;
751              
752 274           SV *symref_sv = NULL;
753 274 50         if (count == 1) {
754 274           symref_sv = POPs;
755 274           SvREFCNT_inc(symref_sv);
756             }
757 274           PUTBACK;
758              
759 274 50         if (!symref_sv || !SvOK(symref_sv)) {
    50          
760 0 0         PUSHMARK(SP);
761 0           PUTBACK;
762 0           call_pv("DynaLoader::dl_error", G_SCALAR);
763 0           SPAGAIN;
764 0           SV *err = POPs;
765 0           warn("XS::JIT: dl_find_symbol failed for %s: %s", boot_name, SvPV_nolen(err));
766 0           PUTBACK;
767 0           SvREFCNT_dec(libref_sv);
768 0 0         FREETMPS;
769 0           LEAVE;
770 0           return 0;
771             }
772              
773             /* dl_install_xsub */
774             char bootstrap_name[300];
775 274           snprintf(bootstrap_name, sizeof(bootstrap_name), "%s::bootstrap", module_name);
776              
777 274 50         PUSHMARK(SP);
778 274 50         XPUSHs(sv_2mortal(newSVpv(bootstrap_name, 0)));
779 274 50         XPUSHs(symref_sv);
780 274 50         XPUSHs(sv_2mortal(newSVpv(so_file, 0)));
781 274           PUTBACK;
782              
783 274           count = call_pv("DynaLoader::dl_install_xsub", G_SCALAR);
784 274           SPAGAIN;
785              
786 274           SV *xs_sv = NULL;
787 274 50         if (count == 1) {
788 274           xs_sv = POPs;
789             }
790 274           PUTBACK;
791              
792 274 50         if (!xs_sv || !SvOK(xs_sv)) {
    50          
793 0           warn("XS::JIT: dl_install_xsub failed for %s", bootstrap_name);
794 0           SvREFCNT_dec(libref_sv);
795 0           SvREFCNT_dec(symref_sv);
796 0 0         FREETMPS;
797 0           LEAVE;
798 0           return 0;
799             }
800              
801             /* Call the boot function */
802 274 50         PUSHMARK(SP);
803 274 50         XPUSHs(sv_2mortal(newSVpv(module_name, 0)));
804 274           PUTBACK;
805              
806 274           call_sv(xs_sv, G_DISCARD | G_EVAL);
807              
808 274 50         if (SvTRUE(ERRSV)) {
    50          
809 0 0         warn("XS::JIT: boot function failed: %s", SvPV_nolen(ERRSV));
810 0           SvREFCNT_dec(libref_sv);
811 0           SvREFCNT_dec(symref_sv);
812 0 0         FREETMPS;
813 0           LEAVE;
814 0           return 0;
815             }
816              
817 274           SvREFCNT_dec(libref_sv);
818 274           SvREFCNT_dec(symref_sv);
819 274 50         FREETMPS;
820 274           LEAVE;
821              
822 274           return 1;
823             }
824              
825             /* Check if any of the target functions are already defined */
826 277           static int functions_already_loaded(pTHX_ XS_JIT_Func *functions, int num_functions) {
827             int i;
828 817 100         for (i = 0; i < num_functions && functions[i].target; i++) {
    50          
829             /* Split target into package and function name */
830 543           const char *target = functions[i].target;
831 543           const char *last_colon = strrchr(target, ':');
832 543 50         if (last_colon && last_colon > target && *(last_colon - 1) == ':') {
    50          
    50          
833             /* Extract package name */
834 543           size_t pkg_len = last_colon - target - 1;
835             char pkg[256];
836 543 50         if (pkg_len >= sizeof(pkg)) pkg_len = sizeof(pkg) - 1;
837 543           strncpy(pkg, target, pkg_len);
838 543           pkg[pkg_len] = '\0';
839              
840             /* Check if function exists in the stash */
841 543           HV *stash = gv_stashpv(pkg, 0);
842 543 100         if (stash) {
843 194           const char *func_name = last_colon + 1;
844 194           GV *gv = (GV*)hv_fetch(stash, func_name, strlen(func_name), 0);
845 194 100         if (gv && *(SV**)gv && GvCV(*(GV**)gv)) {
    50          
    100          
846             /* Function already exists */
847 3           return 1;
848             }
849             }
850             }
851             }
852 274           return 0;
853             }
854              
855             /* Main compile function */
856 278           int xs_jit_compile(pTHX_ const char *code, const char *name,
857             XS_JIT_Func *functions, int num_functions,
858             const char *cache_dir, int force,
859             const char *extra_cflags, const char *extra_ldflags) {
860             char so_path[MAX_PATH_LEN];
861             char c_path[MAX_PATH_LEN];
862             char dir_path[MAX_PATH_LEN];
863              
864             /* Check if already loaded in this process (unless force) */
865 278 100         if (!force && functions_already_loaded(aTHX_ functions, num_functions)) {
    100          
866 3           return 1; /* Already loaded, nothing to do */
867             }
868              
869             /* Build paths */
870 275           const char *dir = get_cache_dir(cache_dir);
871             char safe[256];
872 275           safe_name(name, safe, sizeof(safe));
873              
874 275           HV *config = get_hv("Config::Config", 0);
875 275 50         SV **dlext_sv = config ? hv_fetch(config, "dlext", 5, 0) : NULL;
876 275 50         const char *dlext = (dlext_sv && *dlext_sv) ? SvPV_nolen(*dlext_sv) : "so";
    50          
877              
878 275           snprintf(dir_path, sizeof(dir_path), "%s/lib/auto/%s", dir, safe);
879 275           snprintf(so_path, sizeof(so_path), "%s/%s.%s", dir_path, safe, dlext);
880 275           snprintf(c_path, sizeof(c_path), "%s/%s.c", dir_path, safe);
881              
882             /* Check cache unless force */
883 275 100         if (!force) {
884             Stat_t st;
885 274 50         if (PerlLIO_stat(so_path, &st) == 0) {
886             /* Cached - just load */
887 0           return xs_jit_load(aTHX_ name, so_path);
888             }
889             }
890              
891             /* Generate code */
892 275           char *generated = xs_jit_generate_code(aTHX_ code, name, functions, num_functions);
893 275 50         if (!generated) {
894 0           warn("XS::JIT: Failed to generate code");
895 0           return 0;
896             }
897              
898             /* Create cache directory */
899 275 50         if (!mkdir_p(dir_path)) {
900 0           warn("XS::JIT: Failed to create directory %s", dir_path);
901 0           free(generated);
902 0           return 0;
903             }
904              
905             /* Write C file */
906 275           FILE *fp = fopen(c_path, "w");
907 275 50         if (!fp) {
908 0           warn("XS::JIT: Failed to write %s: %s", c_path, strerror(errno));
909 0           free(generated);
910 0           return 0;
911             }
912 275           fputs(generated, fp);
913 275           fclose(fp);
914 275           free(generated);
915              
916             /* Compile with extra flags */
917 275 100         if (!xs_jit_compile_file(aTHX_ c_path, so_path, extra_cflags, extra_ldflags)) {
918 1           return 0;
919             }
920              
921             /* Load */
922 274           return xs_jit_load(aTHX_ name, so_path);
923             }