Ruby 2.7.6p219 (2022-04-12 revision c9c2245c0a25176072e02db9254f0e0c84c805cd)
mjit_worker.c
Go to the documentation of this file.
1/**********************************************************************
2
3 mjit_worker.c - Worker for MRI method JIT compiler
4
5 Copyright (C) 2017 Vladimir Makarov <vmakarov@redhat.com>.
6
7**********************************************************************/
8
9// NOTE: All functions in this file are executed on MJIT worker. So don't
10// call Ruby methods (C functions that may call rb_funcall) or trigger
11// GC (using ZALLOC, xmalloc, xfree, etc.) in this file.
12
13/* We utilize widely used C compilers (GCC and LLVM Clang) to
14 implement MJIT. We feed them a C code generated from ISEQ. The
15 industrial C compilers are slower than regular JIT engines.
16 Generated code performance of the used C compilers has a higher
17 priority over the compilation speed.
18
19 So our major goal is to minimize the ISEQ compilation time when we
20 use widely optimization level (-O2). It is achieved by
21
22 o Using a precompiled version of the header
23 o Keeping all files in `/tmp`. On modern Linux `/tmp` is a file
24 system in memory. So it is pretty fast
25 o Implementing MJIT as a multi-threaded code because we want to
26 compile ISEQs in parallel with iseq execution to speed up Ruby
27 code execution. MJIT has one thread (*worker*) to do
28 parallel compilations:
29 o It prepares a precompiled code of the minimized header.
30 It starts at the MRI execution start
31 o It generates PIC object files of ISEQs
32 o It takes one JIT unit from a priority queue unless it is empty.
33 o It translates the JIT unit ISEQ into C-code using the precompiled
34 header, calls CC and load PIC code when it is ready
35 o Currently MJIT put ISEQ in the queue when ISEQ is called
36 o MJIT can reorder ISEQs in the queue if some ISEQ has been called
37 many times and its compilation did not start yet
38 o MRI reuses the machine code if it already exists for ISEQ
39 o The machine code we generate can stop and switch to the ISEQ
40 interpretation if some condition is not satisfied as the machine
41 code can be speculative or some exception raises
42 o Speculative machine code can be canceled.
43
44 Here is a diagram showing the MJIT organization:
45
46 _______
47 |header |
48 |_______|
49 | MRI building
50 --------------|----------------------------------------
51 | MRI execution
52 |
53 _____________|_____
54 | | |
55 | ___V__ | CC ____________________
56 | | |----------->| precompiled header |
57 | | | | |____________________|
58 | | | | |
59 | | MJIT | | |
60 | | | | |
61 | | | | ____V___ CC __________
62 | |______|----------->| C code |--->| .so file |
63 | | |________| |__________|
64 | | |
65 | | |
66 | MRI machine code |<-----------------------------
67 |___________________| loading
68
69*/
70
71#ifdef __sun
72#define __EXTENSIONS__ 1
73#endif
74
75#include "vm_core.h"
76#include "mjit.h"
77#include "gc.h"
78#include "ruby_assert.h"
79#include "ruby/debug.h"
80#include "ruby/thread.h"
81
82#ifdef _WIN32
83#include <winsock2.h>
84#include <windows.h>
85#else
86#include <sys/wait.h>
87#include <sys/time.h>
88#include <dlfcn.h>
89#endif
90#include <errno.h>
91#ifdef HAVE_FCNTL_H
92#include <fcntl.h>
93#endif
94#ifdef HAVE_SYS_PARAM_H
95# include <sys/param.h>
96#endif
97#include "dln.h"
98
99#include "ruby/util.h"
100#undef strdup // ruby_strdup may trigger GC
101
102#ifndef MAXPATHLEN
103# define MAXPATHLEN 1024
104#endif
105
106#ifdef _WIN32
107#define dlopen(name,flag) ((void*)LoadLibrary(name))
108#define dlerror() strerror(rb_w32_map_errno(GetLastError()))
109#define dlsym(handle,name) ((void*)GetProcAddress((handle),(name)))
110#define dlclose(handle) (!FreeLibrary(handle))
111#define RTLD_NOW -1
112
113#define waitpid(pid,stat_loc,options) (WaitForSingleObject((HANDLE)(pid), INFINITE), GetExitCodeProcess((HANDLE)(pid), (LPDWORD)(stat_loc)), CloseHandle((HANDLE)pid), (pid))
114#define WIFEXITED(S) ((S) != STILL_ACTIVE)
115#define WEXITSTATUS(S) (S)
116#define WIFSIGNALED(S) (0)
117typedef intptr_t pid_t;
118#endif
119
120// Atomically set function pointer if possible.
121#define MJIT_ATOMIC_SET(var, val) (void)ATOMIC_PTR_EXCHANGE(var, val)
122
123#define MJIT_TMP_PREFIX "_ruby_mjit_"
124
125// The unit structure that holds metadata of ISeq for MJIT.
127 // Unique order number of unit.
128 int id;
129 // Dlopen handle of the loaded object file.
130 void *handle;
132#ifndef _MSC_VER
133 // This value is always set for `compact_all_jit_code`. Also used for lazy deletion.
134 char *o_file;
135 // true if it's inherited from parent Ruby process and lazy deletion should be skipped.
136 // `o_file = NULL` can't be used to skip lazy deletion because `o_file` could be used
137 // by child for `compact_all_jit_code`.
139#endif
140#if defined(_WIN32)
141 // DLL cannot be removed while loaded on Windows. If this is set, it'll be lazily deleted.
142 char *so_file;
143#endif
144 // Only used by unload_units. Flag to check this unit is currently on stack or not.
147 // mjit_compile's optimization switches
149};
150
151// Linked list of struct rb_mjit_unit.
154 int length; // the list length
155};
156
161
167
168// process.c
169extern rb_pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options, rb_nativethread_cond_t *cond);
170
171// A copy of MJIT portion of MRI options since MJIT initialization. We
172// need them as MJIT threads still can work when the most MRI data were
173// freed.
175
176// true if MJIT is enabled.
177bool mjit_enabled = false;
178// true if JIT-ed code should be called. When `ruby_vm_event_enabled_global_flags & ISEQ_TRACE_EVENTS`
179// and `mjit_call_p == false`, any JIT-ed code execution is cancelled as soon as possible.
180bool mjit_call_p = false;
181
182// Priority queue of iseqs waiting for JIT compilation.
183// This variable is a pointer to head unit of the queue.
184static struct rb_mjit_unit_list unit_queue = { LIST_HEAD_INIT(unit_queue.head) };
185// List of units which are successfully compiled.
186static struct rb_mjit_unit_list active_units = { LIST_HEAD_INIT(active_units.head) };
187// List of compacted so files which will be cleaned up by `free_list()` in `mjit_finish()`.
188static struct rb_mjit_unit_list compact_units = { LIST_HEAD_INIT(compact_units.head) };
189// List of units before recompilation and just waiting for dlclose().
190static struct rb_mjit_unit_list stale_units = { LIST_HEAD_INIT(stale_units.head) };
191// The number of so far processed ISEQs, used to generate unique id.
192static int current_unit_num;
193// A mutex for conitionals and critical sections.
194static rb_nativethread_lock_t mjit_engine_mutex;
195// A thread conditional to wake up `mjit_finish` at the end of PCH thread.
196static rb_nativethread_cond_t mjit_pch_wakeup;
197// A thread conditional to wake up the client if there is a change in
198// executed unit status.
199static rb_nativethread_cond_t mjit_client_wakeup;
200// A thread conditional to wake up a worker if there we have something
201// to add or we need to stop MJIT engine.
202static rb_nativethread_cond_t mjit_worker_wakeup;
203// A thread conditional to wake up workers if at the end of GC.
204static rb_nativethread_cond_t mjit_gc_wakeup;
205// True when GC is working.
206static bool in_gc;
207// True when JIT is working.
208static bool in_jit;
209// Set to true to stop worker.
210static bool stop_worker_p;
211// Set to true if worker is stopped.
212static bool worker_stopped;
213
214// Path of "/tmp", which can be changed to $TMP in MinGW.
215static char *tmp_dir;
216// Hash like { 1 => true, 2 => true, ... } whose keys are valid `class_serial`s.
217// This is used to invalidate obsoleted CALL_CACHE.
218static VALUE valid_class_serials;
219
220// Used C compiler path.
221static const char *cc_path;
222// Used C compiler flags.
223static const char **cc_common_args;
224// Used C compiler flags added by --jit-debug=...
225static char **cc_added_args;
226// Name of the precompiled header file.
227static char *pch_file;
228// The process id which should delete the pch_file on mjit_finish.
229static rb_pid_t pch_owner_pid;
230// Status of the precompiled header creation. The status is
231// shared by the workers and the pch thread.
232static enum {PCH_NOT_READY, PCH_FAILED, PCH_SUCCESS} pch_status;
233
234#ifndef _MSC_VER
235// Name of the header file.
236static char *header_file;
237#endif
238
239#ifdef _WIN32
240// Linker option to enable libruby.
241static char *libruby_pathflag;
242#endif
243
244#include "mjit_config.h"
245
246#if defined(__GNUC__) && \
247 (!defined(__clang__) || \
248 (defined(__clang__) && (defined(__FreeBSD__) || defined(__GLIBC__))))
249# define GCC_PIC_FLAGS "-Wfatal-errors", "-fPIC", "-shared", "-w", "-pipe",
250# define MJIT_CFLAGS_PIPE 1
251#else
252# define GCC_PIC_FLAGS /* empty */
253# define MJIT_CFLAGS_PIPE 0
254#endif
255
256// Use `-nodefaultlibs -nostdlib` for GCC where possible, which does not work on mingw, cygwin, AIX, and OpenBSD.
257// This seems to improve MJIT performance on GCC.
258#if defined __GNUC__ && !defined __clang__ && !defined(_WIN32) && !defined(__CYGWIN__) && !defined(_AIX) && !defined(__OpenBSD__)
259# define GCC_NOSTDLIB_FLAGS "-nodefaultlibs", "-nostdlib",
260#else
261# define GCC_NOSTDLIB_FLAGS // empty
262#endif
263
264static const char *const CC_COMMON_ARGS[] = {
266 NULL
267};
268
269static const char *const CC_DEBUG_ARGS[] = {MJIT_DEBUGFLAGS NULL};
270static const char *const CC_OPTIMIZE_ARGS[] = {MJIT_OPTFLAGS NULL};
271
272static const char *const CC_LDSHARED_ARGS[] = {MJIT_LDSHARED GCC_PIC_FLAGS NULL};
273static const char *const CC_DLDFLAGS_ARGS[] = {MJIT_DLDFLAGS NULL};
274// `CC_LINKER_ARGS` are linker flags which must be passed to `-c` as well.
275static const char *const CC_LINKER_ARGS[] = {
276#if defined __GNUC__ && !defined __clang__ && !defined(__OpenBSD__)
277 "-nostartfiles",
278#endif
280};
281
282static const char *const CC_LIBS[] = {
283#if defined(_WIN32) || defined(__CYGWIN__)
284 MJIT_LIBS // mswin, mingw, cygwin
285#endif
286#if defined __GNUC__ && !defined __clang__
287# if defined(_WIN32)
288 "-lmsvcrt", // mingw
289# endif
290 "-lgcc", // mingw, cygwin, and GCC platforms using `-nodefaultlibs -nostdlib`
291#endif
292#if defined __ANDROID__
293 "-lm", // to avoid 'cannot locate symbol "modf" referenced by .../_ruby_mjit_XXX.so"'
294#endif
295 NULL
296};
297
298#define CC_CODEFLAG_ARGS (mjit_opts.debug ? CC_DEBUG_ARGS : CC_OPTIMIZE_ARGS)
299
300// Print the arguments according to FORMAT to stderr only if MJIT
301// verbose option value is more or equal to LEVEL.
302PRINTF_ARGS(static void, 2, 3)
303verbose(int level, const char *format, ...)
304{
305 if (mjit_opts.verbose >= level) {
306 va_list args;
307 size_t len = strlen(format);
308 char *full_format = alloca(sizeof(char) * (len + 2));
309
310 // Creating `format + '\n'` to atomically print format and '\n'.
311 memcpy(full_format, format, len);
312 full_format[len] = '\n';
313 full_format[len+1] = '\0';
314
315 va_start(args, format);
316 vfprintf(stderr, full_format, args);
317 va_end(args);
318 }
319}
320
321PRINTF_ARGS(static void, 1, 2)
322mjit_warning(const char *format, ...)
323{
325 va_list args;
326
327 fprintf(stderr, "MJIT warning: ");
328 va_start(args, format);
329 vfprintf(stderr, format, args);
330 va_end(args);
331 fprintf(stderr, "\n");
332 }
333}
334
335// Add unit node to the tail of doubly linked `list`. It should be not in
336// the list before.
337static void
338add_to_list(struct rb_mjit_unit *unit, struct rb_mjit_unit_list *list)
339{
340 (void)RB_DEBUG_COUNTER_INC_IF(mjit_length_unit_queue, list == &unit_queue);
341 (void)RB_DEBUG_COUNTER_INC_IF(mjit_length_active_units, list == &active_units);
342 (void)RB_DEBUG_COUNTER_INC_IF(mjit_length_compact_units, list == &compact_units);
343 (void)RB_DEBUG_COUNTER_INC_IF(mjit_length_stale_units, list == &stale_units);
344
345 list_add_tail(&list->head, &unit->unode);
346 list->length++;
347}
348
349static void
350remove_from_list(struct rb_mjit_unit *unit, struct rb_mjit_unit_list *list)
351{
352#if USE_DEBUG_COUNTER
353 rb_debug_counter_add(RB_DEBUG_COUNTER_mjit_length_unit_queue, -1, list == &unit_queue);
354 rb_debug_counter_add(RB_DEBUG_COUNTER_mjit_length_active_units, -1, list == &active_units);
355 rb_debug_counter_add(RB_DEBUG_COUNTER_mjit_length_compact_units, -1, list == &compact_units);
356 rb_debug_counter_add(RB_DEBUG_COUNTER_mjit_length_stale_units, -1, list == &stale_units);
357#endif
358
359 list_del(&unit->unode);
360 list->length--;
361}
362
363static void
364remove_file(const char *filename)
365{
366 if (remove(filename)) {
367 mjit_warning("failed to remove \"%s\": %s", filename, strerror(errno));
368 }
369}
370
371// Lazily delete .o and/or .so files.
372static void
373clean_object_files(struct rb_mjit_unit *unit)
374{
375#ifndef _MSC_VER
376 if (unit->o_file) {
377 char *o_file = unit->o_file;
378
379 unit->o_file = NULL;
380 // For compaction, unit->o_file is always set when compilation succeeds.
381 // So save_temps needs to be checked here.
383 remove_file(o_file);
384 free(o_file);
385 }
386#endif
387
388#if defined(_WIN32)
389 if (unit->so_file) {
390 char *so_file = unit->so_file;
391
392 unit->so_file = NULL;
393 // unit->so_file is set only when mjit_opts.save_temps is false.
394 remove_file(so_file);
395 free(so_file);
396 }
397#endif
398}
399
400// This is called in the following situations:
401// 1) On dequeue or `unload_units()`, associated ISeq is already GCed.
402// 2) The unit is not called often and unloaded by `unload_units()`.
403// 3) Freeing lists on `mjit_finish()`.
404//
405// `jit_func` value does not matter for 1 and 3 since the unit won't be used anymore.
406// For the situation 2, this sets the ISeq's JIT state to NOT_COMPILED_JIT_ISEQ_FUNC
407// to prevent the situation that the same methods are continuously compiled.
408static void
409free_unit(struct rb_mjit_unit *unit)
410{
411 if (unit->iseq) { // ISeq is not GCed
413 unit->iseq->body->jit_unit = NULL;
414 }
415 if (unit->handle && dlclose(unit->handle)) { // handle is NULL if it's in queue
416 mjit_warning("failed to close handle for u%d: %s", unit->id, dlerror());
417 }
418 clean_object_files(unit);
419 free(unit);
420}
421
422// Start a critical section. Use message `msg` to print debug info at `level`.
423static inline void
424CRITICAL_SECTION_START(int level, const char *msg)
425{
426 verbose(level, "Locking %s", msg);
427 rb_native_mutex_lock(&mjit_engine_mutex);
428 verbose(level, "Locked %s", msg);
429}
430
431// Finish the current critical section. Use message `msg` to print
432// debug info at `level`.
433static inline void
434CRITICAL_SECTION_FINISH(int level, const char *msg)
435{
436 verbose(level, "Unlocked %s", msg);
437 rb_native_mutex_unlock(&mjit_engine_mutex);
438}
439
440static int
441sprint_uniq_filename(char *str, size_t size, unsigned long id, const char *prefix, const char *suffix)
442{
443 return snprintf(str, size, "%s/%sp%"PRI_PIDT_PREFIX"uu%lu%s", tmp_dir, prefix, getpid(), id, suffix);
444}
445
446// Return time in milliseconds as a double.
447#ifdef __APPLE__
448double ruby_real_ms_time(void);
449# define real_ms_time() ruby_real_ms_time()
450#else
451static double
452real_ms_time(void)
453{
454# ifdef HAVE_CLOCK_GETTIME
455 struct timespec tv;
456# ifdef CLOCK_MONOTONIC
457 const clockid_t c = CLOCK_MONOTONIC;
458# else
459 const clockid_t c = CLOCK_REALTIME;
460# endif
461
462 clock_gettime(c, &tv);
463 return tv.tv_nsec / 1000000.0 + tv.tv_sec * 1000.0;
464# else
465 struct timeval tv;
466
467 gettimeofday(&tv, NULL);
468 return tv.tv_usec / 1000.0 + tv.tv_sec * 1000.0;
469# endif
470}
471#endif
472
473// Return true if class_serial is not obsoleted. This is used by mjit_compile.c.
474bool
476{
477 CRITICAL_SECTION_START(3, "in valid_class_serial_p");
478 bool found_p = rb_hash_stlike_lookup(valid_class_serials, LONG2FIX(class_serial), NULL);
479 CRITICAL_SECTION_FINISH(3, "in valid_class_serial_p");
480 return found_p;
481}
482
483// Return the best unit from list. The best is the first
484// high priority unit or the unit whose iseq has the biggest number
485// of calls so far.
486static struct rb_mjit_unit *
487get_from_list(struct rb_mjit_unit_list *list)
488{
489 struct rb_mjit_unit *unit = NULL, *next, *best = NULL;
490
491 // Find iseq with max total_calls
492 list_for_each_safe(&list->head, unit, next, unode) {
493 if (unit->iseq == NULL) { // ISeq is GCed.
494 remove_from_list(unit, list);
495 free_unit(unit);
496 continue;
497 }
498
499 if (best == NULL || best->iseq->body->total_calls < unit->iseq->body->total_calls) {
500 best = unit;
501 }
502 }
503 if (best) {
504 remove_from_list(best, list);
505 }
506 return best;
507}
508
509// Return length of NULL-terminated array `args` excluding the NULL marker.
510static size_t
511args_len(char *const *args)
512{
513 size_t i;
514
515 for (i = 0; (args[i]) != NULL;i++)
516 ;
517 return i;
518}
519
520// Concatenate `num` passed NULL-terminated arrays of strings, put the
521// result (with NULL end marker) into the heap, and return the result.
522static char **
523form_args(int num, ...)
524{
525 va_list argp;
526 size_t len, n;
527 int i;
528 char **args, **res, **tmp;
529
530 va_start(argp, num);
531 res = NULL;
532 for (i = len = 0; i < num; i++) {
533 args = va_arg(argp, char **);
534 n = args_len(args);
535 if ((tmp = (char **)realloc(res, sizeof(char *) * (len + n + 1))) == NULL) {
536 free(res);
537 res = NULL;
538 break;
539 }
540 res = tmp;
541 MEMCPY(res + len, args, char *, n + 1);
542 len += n;
543 }
544 va_end(argp);
545 return res;
546}
547
549#ifdef __GNUC__
550COMPILER_WARNING_IGNORED(-Wdeprecated-declarations)
551#endif
552// Start an OS process of absolute executable path with arguments `argv`.
553// Return PID of the process.
554static pid_t
555start_process(const char *abspath, char *const *argv)
556{
557 // Not calling non-async-signal-safe functions between vfork
558 // and execv for safety
559 int dev_null = rb_cloexec_open(ruby_null_device, O_WRONLY, 0);
560 if (dev_null < 0) {
561 verbose(1, "MJIT: Failed to open a null device: %s", strerror(errno));
562 return -1;
563 }
564 if (mjit_opts.verbose >= 2) {
565 const char *arg;
566 fprintf(stderr, "Starting process: %s", abspath);
567 for (int i = 0; (arg = argv[i]) != NULL; i++)
568 fprintf(stderr, " %s", arg);
569 fprintf(stderr, "\n");
570 }
571
572 pid_t pid;
573#ifdef _WIN32
574 extern HANDLE rb_w32_start_process(const char *abspath, char *const *argv, int out_fd);
575 int out_fd = 0;
576 if (mjit_opts.verbose <= 1) {
577 // Discard cl.exe's outputs like:
578 // _ruby_mjit_p12u3.c
579 // Creating library C:.../_ruby_mjit_p12u3.lib and object C:.../_ruby_mjit_p12u3.exp
580 out_fd = dev_null;
581 }
582
583 pid = (pid_t)rb_w32_start_process(abspath, argv, out_fd);
584 if (pid == 0) {
585 verbose(1, "MJIT: Failed to create process: %s", dlerror());
586 return -1;
587 }
588#else
589 if ((pid = vfork()) == 0) { /* TODO: reuse some function in process.c */
590 umask(0077);
591 if (mjit_opts.verbose == 0) {
592 // CC can be started in a thread using a file which has been
593 // already removed while MJIT is finishing. Discard the
594 // messages about missing files.
595 dup2(dev_null, STDERR_FILENO);
596 dup2(dev_null, STDOUT_FILENO);
597 }
598 (void)close(dev_null);
599 pid = execv(abspath, argv); // Pid will be negative on an error
600 // Even if we successfully found CC to compile PCH we still can
601 // fail with loading the CC in very rare cases for some reasons.
602 // Stop the forked process in this case.
603 verbose(1, "MJIT: Error in execv: %s", abspath);
604 _exit(1);
605 }
606#endif
607 (void)close(dev_null);
608 return pid;
609}
611
612// Execute an OS process of executable PATH with arguments ARGV.
613// Return -1 or -2 if failed to execute, otherwise exit code of the process.
614// TODO: Use a similar function in process.c
615static int
616exec_process(const char *path, char *const argv[])
617{
618 int stat, exit_code = -2;
619 rb_vm_t *vm = WAITPID_USE_SIGCHLD ? GET_VM() : 0;
621
622 if (vm) {
625 }
626
627 pid_t pid = start_process(path, argv);
628 for (;pid > 0;) {
629 pid_t r = vm ? ruby_waitpid_locked(vm, pid, &stat, 0, &cond)
630 : waitpid(pid, &stat, 0);
631 if (r == -1) {
632 if (errno == EINTR) continue;
633 fprintf(stderr, "[%"PRI_PIDT_PREFIX"d] waitpid(%lu): %s (SIGCHLD=%d,%u)\n",
634 getpid(), (unsigned long)pid, strerror(errno),
636 break;
637 }
638 else if (r == pid) {
639 if (WIFEXITED(stat)) {
640 exit_code = WEXITSTATUS(stat);
641 break;
642 }
643 else if (WIFSIGNALED(stat)) {
644 exit_code = -1;
645 break;
646 }
647 }
648 }
649
650 if (vm) {
653 }
654 return exit_code;
655}
656
657static void
658remove_so_file(const char *so_file, struct rb_mjit_unit *unit)
659{
660#if defined(_WIN32)
661 // Windows can't remove files while it's used.
662 unit->so_file = strdup(so_file); // lazily delete on `clean_object_files()`
663 if (unit->so_file == NULL)
664 mjit_warning("failed to allocate memory to lazily remove '%s': %s", so_file, strerror(errno));
665#else
666 remove_file(so_file);
667#endif
668}
669
670#define append_str2(p, str, len) ((char *)memcpy((p), str, (len))+(len))
671#define append_str(p, str) append_str2(p, str, sizeof(str)-1)
672#define append_lit(p, str) append_str2(p, str, rb_strlen_lit(str))
673
674#ifdef _MSC_VER
675// Compile C file to so. It returns true if it succeeds. (mswin)
676static bool
677compile_c_to_so(const char *c_file, const char *so_file)
678{
679 const char *files[] = { NULL, NULL, NULL, NULL, NULL, NULL, "-link", libruby_pathflag, NULL };
680 char *p;
681
682 // files[0] = "-Fe*.dll"
683 files[0] = p = alloca(sizeof(char) * (rb_strlen_lit("-Fe") + strlen(so_file) + 1));
684 p = append_lit(p, "-Fe");
685 p = append_str2(p, so_file, strlen(so_file));
686 *p = '\0';
687
688 // files[1] = "-Fo*.obj"
689 // We don't need .obj file, but it's somehow created to cwd without -Fo and we want to control the output directory.
690 files[1] = p = alloca(sizeof(char) * (rb_strlen_lit("-Fo") + strlen(so_file) - rb_strlen_lit(DLEXT) + rb_strlen_lit(".obj") + 1));
691 char *obj_file = p = append_lit(p, "-Fo");
692 p = append_str2(p, so_file, strlen(so_file) - rb_strlen_lit(DLEXT));
693 p = append_lit(p, ".obj");
694 *p = '\0';
695
696 // files[2] = "-Yu*.pch"
697 files[2] = p = alloca(sizeof(char) * (rb_strlen_lit("-Yu") + strlen(pch_file) + 1));
698 p = append_lit(p, "-Yu");
699 p = append_str2(p, pch_file, strlen(pch_file));
700 *p = '\0';
701
702 // files[3] = "C:/.../rb_mjit_header-*.obj"
703 files[3] = p = alloca(sizeof(char) * (strlen(pch_file) + 1));
704 p = append_str2(p, pch_file, strlen(pch_file) - strlen(".pch"));
705 p = append_lit(p, ".obj");
706 *p = '\0';
707
708 // files[4] = "-Tc*.c"
709 files[4] = p = alloca(sizeof(char) * (rb_strlen_lit("-Tc") + strlen(c_file) + 1));
710 p = append_lit(p, "-Tc");
711 p = append_str2(p, c_file, strlen(c_file));
712 *p = '\0';
713
714 // files[5] = "-Fd*.pdb"
715 files[5] = p = alloca(sizeof(char) * (rb_strlen_lit("-Fd") + strlen(pch_file) + 1));
716 p = append_lit(p, "-Fd");
717 p = append_str2(p, pch_file, strlen(pch_file) - rb_strlen_lit(".pch"));
718 p = append_lit(p, ".pdb");
719 *p = '\0';
720
721 char **args = form_args(5, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
722 files, CC_LIBS, CC_DLDFLAGS_ARGS);
723 if (args == NULL)
724 return false;
725
726 int exit_code = exec_process(cc_path, args);
727 free(args);
728
729 if (exit_code == 0) {
730 // remove never-used files (.obj, .lib, .exp, .pdb). XXX: Is there any way not to generate this?
731 if (!mjit_opts.save_temps) {
732 char *before_dot;
733 remove_file(obj_file);
734
735 before_dot = obj_file + strlen(obj_file) - rb_strlen_lit(".obj");
736 append_lit(before_dot, ".lib"); remove_file(obj_file);
737 append_lit(before_dot, ".exp"); remove_file(obj_file);
738 append_lit(before_dot, ".pdb"); remove_file(obj_file);
739 }
740 }
741 else {
742 verbose(2, "compile_c_to_so: compile error: %d", exit_code);
743 }
744 return exit_code == 0;
745}
746#else // _MSC_VER
747
748// The function producing the pre-compiled header.
749static void
750make_pch(void)
751{
752 const char *rest_args[] = {
753# ifdef __clang__
754 "-emit-pch",
755 "-c",
756# endif
757 // -nodefaultlibs is a linker flag, but it may affect cc1 behavior on Gentoo, which should NOT be changed on pch:
758 // https://gitweb.gentoo.org/proj/gcc-patches.git/tree/7.3.0/gentoo/13_all_default-ssp-fix.patch
760 "-o", pch_file, header_file,
761 NULL,
762 };
763
764 verbose(2, "Creating precompiled header");
765 char **args = form_args(4, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, rest_args);
766 if (args == NULL) {
767 mjit_warning("making precompiled header failed on forming args");
768 CRITICAL_SECTION_START(3, "in make_pch");
769 pch_status = PCH_FAILED;
770 CRITICAL_SECTION_FINISH(3, "in make_pch");
771 return;
772 }
773
774 int exit_code = exec_process(cc_path, args);
775 free(args);
776
777 CRITICAL_SECTION_START(3, "in make_pch");
778 if (exit_code == 0) {
779 pch_status = PCH_SUCCESS;
780 }
781 else {
782 mjit_warning("Making precompiled header failed on compilation. Stopping MJIT worker...");
783 pch_status = PCH_FAILED;
784 }
785 /* wakeup `mjit_finish` */
786 rb_native_cond_broadcast(&mjit_pch_wakeup);
787 CRITICAL_SECTION_FINISH(3, "in make_pch");
788}
789
790// Compile .c file to .o file. It returns true if it succeeds. (non-mswin)
791static bool
792compile_c_to_o(const char *c_file, const char *o_file)
793{
794 const char *files[] = {
795 "-o", o_file, c_file,
796# ifdef __clang__
797 "-include-pch", pch_file,
798# endif
799 "-c", NULL
800 };
801
802 char **args = form_args(5, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, files, CC_LINKER_ARGS);
803 if (args == NULL)
804 return false;
805
806 int exit_code = exec_process(cc_path, args);
807 free(args);
808
809 if (exit_code != 0)
810 verbose(2, "compile_c_to_o: compile error: %d", exit_code);
811 return exit_code == 0;
812}
813
814// Link .o files to .so file. It returns true if it succeeds. (non-mswin)
815static bool
816link_o_to_so(const char **o_files, const char *so_file)
817{
818 const char *options[] = {
819 "-o", so_file,
820# ifdef _WIN32
821 libruby_pathflag,
822# endif
823 NULL
824 };
825
826 char **args = form_args(7, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
827 options, o_files, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS);
828 if (args == NULL)
829 return false;
830
831 int exit_code = exec_process(cc_path, args);
832 free(args);
833
834 if (exit_code != 0)
835 verbose(2, "link_o_to_so: link error: %d", exit_code);
836 return exit_code == 0;
837}
838
839// Link all cached .o files and build a .so file. Reload all JIT func from it. This
840// allows to avoid JIT code fragmentation and improve performance to call JIT-ed code.
841static void
842compact_all_jit_code(void)
843{
844# ifndef _WIN32 // This requires header transformation but we don't transform header on Windows for now
845 struct rb_mjit_unit *unit, *cur = 0;
846 double start_time, end_time;
847 static const char so_ext[] = DLEXT;
848 char so_file[MAXPATHLEN];
849 const char **o_files;
850 int i = 0;
851
852 // Abnormal use case of rb_mjit_unit that doesn't have ISeq
853 unit = calloc(1, sizeof(struct rb_mjit_unit)); // To prevent GC, don't use ZALLOC
854 if (unit == NULL) return;
855 unit->id = current_unit_num++;
856 sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
857
858 // NULL-ending for form_args
859 o_files = alloca(sizeof(char *) * (active_units.length + 1));
860 o_files[active_units.length] = NULL;
861 CRITICAL_SECTION_START(3, "in compact_all_jit_code to keep .o files");
862 list_for_each(&active_units.head, cur, unode) {
863 o_files[i] = cur->o_file;
864 i++;
865 }
866
867 start_time = real_ms_time();
868 bool success = link_o_to_so(o_files, so_file);
869 end_time = real_ms_time();
870
871 // TODO: Shrink this big critical section. For now, this is needed to prevent failure by missing .o files.
872 // This assumes that o -> so link doesn't take long time because the bottleneck, which is compiler optimization,
873 // is already done. But actually it takes about 500ms for 5,000 methods on my Linux machine, so it's better to
874 // finish this critical section before link_o_to_so by disabling unload_units.
875 CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to keep .o files");
876
877 if (success) {
878 void *handle = dlopen(so_file, RTLD_NOW);
879 if (handle == NULL) {
880 mjit_warning("failure in loading code from compacted '%s': %s", so_file, dlerror());
881 free(unit);
882 return;
883 }
884 unit->handle = handle;
885
886 // lazily dlclose handle (and .so file for win32) on `mjit_finish()`.
887 add_to_list(unit, &compact_units);
888
890 remove_so_file(so_file, unit);
891
892 CRITICAL_SECTION_START(3, "in compact_all_jit_code to read list");
893 list_for_each(&active_units.head, cur, unode) {
894 void *func;
895 char funcname[35]; // TODO: reconsider `35`
896 sprintf(funcname, "_mjit%d", cur->id);
897
898 if ((func = dlsym(handle, funcname)) == NULL) {
899 mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror());
900 continue;
901 }
902
903 if (cur->iseq) { // Check whether GCed or not
904 // Usage of jit_code might be not in a critical section.
906 }
907 }
908 CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to read list");
909 verbose(1, "JIT compaction (%.1fms): Compacted %d methods -> %s", end_time - start_time, active_units.length, so_file);
910 }
911 else {
912 free(unit);
913 verbose(1, "JIT compaction failure (%.1fms): Failed to compact methods", end_time - start_time);
914 }
915# endif // _WIN32
916}
917
918#endif // _MSC_VER
919
920static void *
921load_func_from_so(const char *so_file, const char *funcname, struct rb_mjit_unit *unit)
922{
923 void *handle, *func;
924
925 handle = dlopen(so_file, RTLD_NOW);
926 if (handle == NULL) {
927 mjit_warning("failure in loading code from '%s': %s", so_file, dlerror());
928 return (void *)NOT_ADDED_JIT_ISEQ_FUNC;
929 }
930
931 func = dlsym(handle, funcname);
932 unit->handle = handle;
933 return func;
934}
935
936#ifndef __clang__
937static const char *
938header_name_end(const char *s)
939{
940 const char *e = s + strlen(s);
941# ifdef __GNUC__ // don't chomp .pch for mswin
942 static const char suffix[] = ".gch";
943
944 // chomp .gch suffix
945 if (e > s+sizeof(suffix)-1 && strcmp(e-sizeof(suffix)+1, suffix) == 0) {
946 e -= sizeof(suffix)-1;
947 }
948# endif
949 return e;
950}
951#endif
952
953// Print platform-specific prerequisites in generated code.
954static void
955compile_prelude(FILE *f)
956{
957#ifndef __clang__ // -include-pch is used for Clang
958 const char *s = pch_file;
959 const char *e = header_name_end(s);
960
961 fprintf(f, "#include \"");
962 // print pch_file except .gch for gcc, but keep .pch for mswin
963 for (; s < e; s++) {
964 switch(*s) {
965 case '\\': case '"':
966 fputc('\\', f);
967 }
968 fputc(*s, f);
969 }
970 fprintf(f, "\"\n");
971#endif
972
973#ifdef _WIN32
974 fprintf(f, "void _pei386_runtime_relocator(void){}\n");
975 fprintf(f, "int __stdcall DllMainCRTStartup(void* hinstDLL, unsigned int fdwReason, void* lpvReserved) { return 1; }\n");
976#endif
977}
978
979// Compile ISeq in UNIT and return function pointer of JIT-ed code.
980// It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong.
981static mjit_func_t
982convert_unit_to_func(struct rb_mjit_unit *unit)
983{
984 char c_file_buff[MAXPATHLEN], *c_file = c_file_buff, *so_file, funcname[35]; // TODO: reconsider `35`
985 int fd;
986 FILE *f;
987 void *func;
988 double start_time, end_time;
989 int c_file_len = (int)sizeof(c_file_buff);
990 static const char c_ext[] = ".c";
991 static const char so_ext[] = DLEXT;
992 const int access_mode =
993#ifdef O_BINARY
994 O_BINARY|
995#endif
996 O_WRONLY|O_EXCL|O_CREAT;
997#ifndef _MSC_VER
998 static const char o_ext[] = ".o";
999 char *o_file;
1000#endif
1001
1002 c_file_len = sprint_uniq_filename(c_file_buff, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1003 if (c_file_len >= (int)sizeof(c_file_buff)) {
1004 ++c_file_len;
1005 c_file = alloca(c_file_len);
1006 c_file_len = sprint_uniq_filename(c_file, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1007 }
1008 ++c_file_len;
1009
1010#ifndef _MSC_VER
1011 o_file = alloca(c_file_len - sizeof(c_ext) + sizeof(o_ext));
1012 memcpy(o_file, c_file, c_file_len - sizeof(c_ext));
1013 memcpy(&o_file[c_file_len - sizeof(c_ext)], o_ext, sizeof(o_ext));
1014#endif
1015 so_file = alloca(c_file_len - sizeof(c_ext) + sizeof(so_ext));
1016 memcpy(so_file, c_file, c_file_len - sizeof(c_ext));
1017 memcpy(&so_file[c_file_len - sizeof(c_ext)], so_ext, sizeof(so_ext));
1018
1019 sprintf(funcname, "_mjit%d", unit->id);
1020
1021 fd = rb_cloexec_open(c_file, access_mode, 0600);
1022 if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
1023 int e = errno;
1024 if (fd >= 0) (void)close(fd);
1025 verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
1027 }
1028
1029 // print #include of MJIT header, etc.
1030 compile_prelude(f);
1031
1032 // wait until mjit_gc_exit_hook is called
1033 CRITICAL_SECTION_START(3, "before mjit_compile to wait GC finish");
1034 while (in_gc) {
1035 verbose(3, "Waiting wakeup from GC");
1036 rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1037 }
1038
1039 // We need to check again here because we could've waited on GC above
1040 if (unit->iseq == NULL) {
1041 fclose(f);
1042 if (!mjit_opts.save_temps)
1043 remove_file(c_file);
1044 in_jit = false; // just being explicit for return
1045 }
1046 else {
1047 in_jit = true;
1048 }
1049 CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish");
1050 if (!in_jit) {
1052 }
1053
1054 // To make MJIT worker thread-safe against GC.compact, copy ISeq values while `in_jit` is true.
1055 long iseq_lineno = 0;
1056 if (FIXNUM_P(unit->iseq->body->location.first_lineno))
1057 // FIX2INT may fallback to rb_num2long(), which is a method call and dangerous in MJIT worker. So using only FIX2LONG.
1058 iseq_lineno = FIX2LONG(unit->iseq->body->location.first_lineno);
1059 char *iseq_label = alloca(RSTRING_LEN(unit->iseq->body->location.label) + 1);
1060 char *iseq_path = alloca(RSTRING_LEN(rb_iseq_path(unit->iseq)) + 1);
1061 strcpy(iseq_label, RSTRING_PTR(unit->iseq->body->location.label));
1062 strcpy(iseq_path, RSTRING_PTR(rb_iseq_path(unit->iseq)));
1063
1064 verbose(2, "start compilation: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1065 fprintf(f, "/* %s@%s:%ld */\n\n", iseq_label, iseq_path, iseq_lineno);
1066 bool success = mjit_compile(f, unit->iseq, funcname);
1067
1068 // release blocking mjit_gc_start_hook
1069 CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC");
1070 in_jit = false;
1071 verbose(3, "Sending wakeup signal to client in a mjit-worker for GC");
1072 rb_native_cond_signal(&mjit_client_wakeup);
1073 CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC");
1074
1075 fclose(f);
1076 if (!success) {
1077 if (!mjit_opts.save_temps)
1078 remove_file(c_file);
1079 verbose(1, "JIT failure: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1081 }
1082
1083 start_time = real_ms_time();
1084#ifdef _MSC_VER
1085 success = compile_c_to_so(c_file, so_file);
1086#else
1087 // splitting .c -> .o step and .o -> .so step, to cache .o files in the future
1088 if ((success = compile_c_to_o(c_file, o_file)) != false) {
1089 success = link_o_to_so((const char *[]){ o_file, NULL }, so_file);
1090
1091 // Always set o_file for compaction. The value is also used for lazy deletion.
1092 unit->o_file = strdup(o_file);
1093 if (unit->o_file == NULL) {
1094 mjit_warning("failed to allocate memory to remember '%s' (%s), removing it...", o_file, strerror(errno));
1095 remove_file(o_file);
1096 }
1097 }
1098#endif
1099 end_time = real_ms_time();
1100
1101 if (!mjit_opts.save_temps)
1102 remove_file(c_file);
1103 if (!success) {
1104 verbose(2, "Failed to generate so: %s", so_file);
1106 }
1107
1108 func = load_func_from_so(so_file, funcname, unit);
1109 if (!mjit_opts.save_temps)
1110 remove_so_file(so_file, unit);
1111
1112 if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1113 verbose(1, "JIT success (%.1fms): %s@%s:%ld -> %s",
1114 end_time - start_time, iseq_label, iseq_path, iseq_lineno, c_file);
1115 }
1116 return (mjit_func_t)func;
1117}
1118
1119typedef struct {
1125
1126// Singleton MJIT copy job. This is made global since it needs to be durable even when MJIT worker thread is stopped.
1127// (ex: register job -> MJIT pause -> MJIT resume -> dispatch job. Actually this should be just cancelled by finish_p check)
1128static mjit_copy_job_t mjit_copy_job = { .iseq = NULL, .finish_p = true };
1129
1130static void mjit_copy_job_handler(void *data);
1131
1132// vm_trace.c
1133int rb_workqueue_register(unsigned flags, rb_postponed_job_func_t , void *);
1134
1135// Copy inline cache values of `iseq` to `cc_entries` and `is_entries`.
1136// These buffers should be pre-allocated properly prior to calling this function.
1137// Return true if copy succeeds or is not needed.
1138//
1139// We're lazily copying cache values from main thread because these cache values
1140// could be different between ones on enqueue timing and ones on dequeue timing.
1141bool
1143{
1144 mjit_copy_job_t *job = &mjit_copy_job; // just a short hand
1145
1146 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1147 job->finish_p = true; // disable dispatching this job in mjit_copy_job_handler while it's being modified
1148 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1149
1150 job->cc_entries = cc_entries;
1151 job->is_entries = is_entries;
1152
1153 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1154 job->iseq = iseq; // Prevernt GC of this ISeq from here
1155 VM_ASSERT(in_jit);
1156 in_jit = false; // To avoid deadlock, allow running GC while waiting for copy job
1157 rb_native_cond_signal(&mjit_client_wakeup); // Unblock main thread waiting in `mjit_gc_start_hook`
1158
1159 job->finish_p = false; // allow dispatching this job in mjit_copy_job_handler
1160 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1161
1162 if (UNLIKELY(mjit_opts.wait)) {
1163 mjit_copy_job_handler((void *)job);
1164 }
1165 else if (rb_workqueue_register(0, mjit_copy_job_handler, (void *)job)) {
1166 CRITICAL_SECTION_START(3, "in MJIT copy job wait");
1167 // checking `stop_worker_p` too because `RUBY_VM_CHECK_INTS(ec)` may not
1168 // lush mjit_copy_job_handler when EC_EXEC_TAG() is not TAG_NONE, and then
1169 // `stop_worker()` could dead lock with this function.
1170 while (!job->finish_p && !stop_worker_p) {
1171 rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1172 verbose(3, "Getting wakeup from client");
1173 }
1174 CRITICAL_SECTION_FINISH(3, "in MJIT copy job wait");
1175 }
1176
1177 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1178 bool success_p = job->finish_p;
1179 // Disable dispatching this job in mjit_copy_job_handler while memory allocated by alloca
1180 // could be expired after finishing this function.
1181 job->finish_p = true;
1182
1183 in_jit = true; // Prohibit GC during JIT compilation
1184 if (job->iseq == NULL) // ISeq GC is notified in mjit_mark_iseq
1185 success_p = false;
1186 job->iseq = NULL; // Allow future GC of this ISeq from here
1187 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1188 return success_p;
1189}
1190
1191// The function implementing a worker. It is executed in a separate
1192// thread by rb_thread_create_mjit_thread. It compiles precompiled header
1193// and then compiles requested ISeqs.
1194void
1196{
1197#ifndef _MSC_VER
1198 if (pch_status == PCH_NOT_READY) {
1199 make_pch();
1200 }
1201#endif
1202 if (pch_status == PCH_FAILED) {
1203 mjit_enabled = false;
1204 CRITICAL_SECTION_START(3, "in worker to update worker_stopped");
1205 worker_stopped = true;
1206 verbose(3, "Sending wakeup signal to client in a mjit-worker");
1207 rb_native_cond_signal(&mjit_client_wakeup);
1208 CRITICAL_SECTION_FINISH(3, "in worker to update worker_stopped");
1209 return; // TODO: do the same thing in the latter half of mjit_finish
1210 }
1211
1212 // main worker loop
1213 while (!stop_worker_p) {
1214 struct rb_mjit_unit *unit;
1215
1216 // wait until unit is available
1217 CRITICAL_SECTION_START(3, "in worker dequeue");
1218 while ((list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) {
1219 rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1220 verbose(3, "Getting wakeup from client");
1221 }
1222 unit = get_from_list(&unit_queue);
1223 CRITICAL_SECTION_FINISH(3, "in worker dequeue");
1224
1225 if (unit) {
1226 // JIT compile
1227 mjit_func_t func = convert_unit_to_func(unit);
1228 (void)RB_DEBUG_COUNTER_INC_IF(mjit_compile_failures, func == (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC);
1229
1230 CRITICAL_SECTION_START(3, "in jit func replace");
1231 while (in_gc) { // Make sure we're not GC-ing when touching ISeq
1232 verbose(3, "Waiting wakeup from GC");
1233 rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1234 }
1235 if (unit->iseq) { // Check whether GCed or not
1236 if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1237 add_to_list(unit, &active_units);
1238 }
1239 // Usage of jit_code might be not in a critical section.
1240 MJIT_ATOMIC_SET(unit->iseq->body->jit_func, func);
1241 }
1242 else {
1243 free_unit(unit);
1244 }
1245 CRITICAL_SECTION_FINISH(3, "in jit func replace");
1246
1247#ifndef _MSC_VER
1248 // Combine .o files to one .so and reload all jit_func to improve memory locality
1249 if ((!mjit_opts.wait && unit_queue.length == 0 && active_units.length > 1)
1250 || active_units.length == mjit_opts.max_cache_size) {
1251 compact_all_jit_code();
1252 }
1253#endif
1254 }
1255 }
1256
1257 // To keep mutex unlocked when it is destroyed by mjit_finish, don't wrap CRITICAL_SECTION here.
1258 worker_stopped = true;
1259}
#define O_BINARY
Definition: _sdbm.c:87
int errno
void(* rb_postponed_job_func_t)(void *arg)
Definition: debug.h:91
#define free(x)
Definition: dln.c:52
struct rb_encoding_entry * list
Definition: encoding.c:56
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
#define MJIT_CFLAGS
Definition: mjit_config.h:11
#define MJIT_CC_COMMON
Definition: mjit_config.h:10
#define MJIT_LIBS
Definition: mjit_config.h:16
#define MJIT_DLDFLAGS
Definition: mjit_config.h:15
#define MJIT_DEBUGFLAGS
Definition: mjit_config.h:13
#define MJIT_LDSHARED
Definition: mjit_config.h:14
#define MJIT_OPTFLAGS
Definition: mjit_config.h:12
rb_pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options, rb_nativethread_cond_t *cond)
Definition: process.c:1097
@ PCH_SUCCESS
Definition: mjit_worker.c:232
@ PCH_FAILED
Definition: mjit_worker.c:232
@ PCH_NOT_READY
Definition: mjit_worker.c:232
void mjit_worker(void)
Definition: mjit_worker.c:1195
verbose(int level, const char *format,...)
Definition: mjit_worker.c:303
int rb_workqueue_register(unsigned flags, rb_postponed_job_func_t, void *)
Definition: vm_trace.c:1643
bool mjit_valid_class_serial_p(rb_serial_t class_serial)
Definition: mjit_worker.c:475
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
void rb_native_cond_initialize(rb_nativethread_cond_t *cond)
#define append_str2(p, str, len)
Definition: mjit_worker.c:670
void rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
struct mjit_options mjit_opts
Definition: mjit_worker.c:174
bool mjit_copy_cache_from_main_thread(const rb_iseq_t *iseq, struct rb_call_cache *cc_entries, union iseq_inline_storage_entry *is_entries)
Definition: mjit_worker.c:1142
#define CC_CODEFLAG_ARGS
Definition: mjit_worker.c:298
mjit_warning(const char *format,...)
Definition: mjit_worker.c:322
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
#define GCC_NOSTDLIB_FLAGS
Definition: mjit_worker.c:261
#define GCC_PIC_FLAGS
Definition: mjit_worker.c:252
bool mjit_call_p
Definition: mjit_worker.c:180
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
#define MJIT_TMP_PREFIX
Definition: mjit_worker.c:123
bool mjit_enabled
Definition: mjit_worker.c:177
void rb_native_cond_destroy(rb_nativethread_cond_t *cond)
void rb_native_cond_signal(rb_nativethread_cond_t *cond)
#define MJIT_ATOMIC_SET(var, val)
Definition: mjit_worker.c:121
#define MAXPATHLEN
Definition: mjit_worker.c:103
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
#define append_lit(p, str)
Definition: mjit_worker.c:672
pid_t vfork(void)
#define alloca(size)
@ RB_DEBUG_COUNTER_mjit_length_compact_units
@ RB_DEBUG_COUNTER_mjit_length_active_units
@ RB_DEBUG_COUNTER_mjit_length_stale_units
@ RB_DEBUG_COUNTER_mjit_length_unit_queue
#define MEMCPY(p1, p2, type, n)
#define list_del(n)
#define NULL
VALUE rb_iseq_path(const rb_iseq_t *iseq)
Definition: iseq.c:1027
#define RSTRING_LEN(str)
int clock_gettime(clockid_t clock_id, struct timespec *tp)
Definition: win32.c:4642
int sprintf(char *__restrict__, const char *__restrict__,...) __attribute__((__format__(__printf__
#define COMPILER_WARNING_PUSH
size_t strlen(const char *)
int strcmp(const char *, const char *)
int execv(const char *__path, char *const __argv[])
int close(int __fildes)
#define SIGCHLD_LOSSY
#define LONG2FIX(i)
__clockid_t clockid_t
int int int int int int vfprintf(FILE *__restrict__, const char *__restrict__, __gnuc_va_list) __attribute__((__format__(__printf__
int fputc(int, FILE *)
#define WAITPID_USE_SIGCHLD
#define RSTRING_PTR(str)
char * strerror(int)
Definition: strerror.c:11
VALUE(* mjit_func_t)(rb_execution_context_t *, rb_control_frame_t *)
int snprintf(char *__restrict__, size_t, const char *__restrict__,...) __attribute__((__format__(__printf__
#define LIST_HEAD_INIT(name)
#define EINTR
__intptr_t intptr_t
#define VM_ASSERT(expr)
#define list_add_tail(h, n)
_Bool mjit_compile(FILE *f, const rb_iseq_t *iseq, const char *funcname)
char * strcpy(char *__restrict__, const char *__restrict__)
#define COMPILER_WARNING_POP
int fprintf(FILE *__restrict__, const char *__restrict__,...) __attribute__((__format__(__printf__
const char size_t n
#define WIFEXITED(_w)
unsigned long VALUE
#define stderr
void * realloc(void *, size_t) __attribute__((__warn_unused_result__)) __attribute__((__alloc_size__(2)))
__pid_t pid_t
int fclose(FILE *)
void * calloc(size_t, size_t) __attribute__((__malloc__)) __attribute__((__warn_unused_result__)) __attribute__((__alloc_size__(1
#define GET_VM()
uint32_t i
#define CLOCK_MONOTONIC
int int remove(const char *)
__inline__ const void *__restrict__ size_t len
int rb_cloexec_open(const char *pathname, int flags, mode_t mode)
Definition: io.c:292
const char ruby_null_device[]
Definition: file.c:6410
int dup2(int __fildes, int __fildes2)
Definition: dup2.c:27
#define va_end(v)
__gnuc_va_list va_list
unsigned long long rb_serial_t
void _exit(int __status) __attribute__((__noreturn__))
mode_t umask(mode_t __mask)
#define CLOCK_REALTIME
#define PRI_PIDT_PREFIX
@ NOT_COMPILED_JIT_ISEQ_FUNC
@ NOT_ADDED_JIT_ISEQ_FUNC
#define list_empty(h)
#define list_for_each_safe(h, i, nxt, member)
int int int int int int int int char char int int int int int int int int int char char int int int int int int int int FILE * fdopen(int, const char *)
#define va_arg(v, l)
#define RB_DEBUG_COUNTER_INC_IF(type, cond)
#define PRINTF_ARGS(decl, string_index, first_to_check)
#define va_start(v, l)
#define rb_pid_t
const rb_iseq_t * iseq
#define DLEXT
unsigned int size
#define WIFSIGNALED(_w)
char * strdup(const char *) __attribute__((__malloc__)) __attribute__((__warn_unused_result__))
#define rb_strlen_lit(str)
#define UNLIKELY(x)
__uintptr_t uintptr_t
int stat(const char *__restrict__ __path, struct stat *__restrict__ __sbuf)
void * memcpy(void *__restrict__, const void *__restrict__, size_t)
#define STDOUT_FILENO
#define list_for_each(h, i, member)
pid_t getpid(void)
const VALUE * argv
__inline__ int
#define FIXNUM_P(f)
#define STDERR_FILENO
#define FIX2LONG(x)
#define COMPILER_WARNING_IGNORED(flag)
#define RUBY_SIGCHLD
int rb_hash_stlike_lookup(VALUE hash, st_data_t key, st_data_t *pval)
Definition: hash.c:2017
#define WEXITSTATUS(_w)
#define f
#define const
Definition: strftime.c:103
struct rb_call_cache * cc_entries
Definition: mjit_worker.c:1121
union iseq_inline_storage_entry * is_entries
Definition: mjit_worker.c:1122
const rb_iseq_t * iseq
Definition: mjit_worker.c:1120
VALUE(* jit_func)(struct rb_execution_context_struct *, struct rb_control_frame_struct *)
struct rb_mjit_unit * jit_unit
struct rb_iseq_constant_body * body
struct list_head head
Definition: mjit_worker.c:153
void * handle
Definition: mjit_worker.c:130
rb_iseq_t * iseq
Definition: mjit_worker.c:131
bool o_file_inherited_p
Definition: mjit_worker.c:138
struct rb_mjit_compile_info compile_info
Definition: mjit_worker.c:148
char used_code_p
Definition: mjit_worker.c:145
char * o_file
Definition: mjit_worker.c:134
struct list_node unode
Definition: mjit_worker.c:146
rb_nativethread_lock_t waitpid_lock
MJIT_FUNC_EXPORTED HANDLE rb_w32_start_process(const char *abspath, char *const *argv, int out_fd)
Definition: win32.c:1323
int gettimeofday(struct timeval *, struct timezone *)
Definition: win32.c:4628
rb_pid_t waitpid(rb_pid_t, int *, int)
Definition: win32.c:4506