Ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5ad0e4688e963d9de019557c78feed9)
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# if defined(__MACH__)
827 extern VALUE rb_libruby_selfpath;
828 const char *loader_args[] = {"-bundle_loader", StringValuePtr(rb_libruby_selfpath), NULL};
829# else
830 const char *loader_args[] = {NULL};
831# endif
832
833 char **args = form_args(8, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
834 options, o_files, loader_args, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS);
835 if (args == NULL)
836 return false;
837
838 int exit_code = exec_process(cc_path, args);
839 free(args);
840
841 if (exit_code != 0)
842 verbose(2, "link_o_to_so: link error: %d", exit_code);
843 return exit_code == 0;
844}
845
846// Link all cached .o files and build a .so file. Reload all JIT func from it. This
847// allows to avoid JIT code fragmentation and improve performance to call JIT-ed code.
848static void
849compact_all_jit_code(void)
850{
851# ifndef _WIN32 // This requires header transformation but we don't transform header on Windows for now
852 struct rb_mjit_unit *unit, *cur = 0;
853 double start_time, end_time;
854 static const char so_ext[] = DLEXT;
855 char so_file[MAXPATHLEN];
856 const char **o_files;
857 int i = 0;
858
859 // Abnormal use case of rb_mjit_unit that doesn't have ISeq
860 unit = calloc(1, sizeof(struct rb_mjit_unit)); // To prevent GC, don't use ZALLOC
861 if (unit == NULL) return;
862 unit->id = current_unit_num++;
863 sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
864
865 // NULL-ending for form_args
866 o_files = alloca(sizeof(char *) * (active_units.length + 1));
867 o_files[active_units.length] = NULL;
868 CRITICAL_SECTION_START(3, "in compact_all_jit_code to keep .o files");
869 list_for_each(&active_units.head, cur, unode) {
870 o_files[i] = cur->o_file;
871 i++;
872 }
873
874 start_time = real_ms_time();
875 bool success = link_o_to_so(o_files, so_file);
876 end_time = real_ms_time();
877
878 // TODO: Shrink this big critical section. For now, this is needed to prevent failure by missing .o files.
879 // This assumes that o -> so link doesn't take long time because the bottleneck, which is compiler optimization,
880 // is already done. But actually it takes about 500ms for 5,000 methods on my Linux machine, so it's better to
881 // finish this critical section before link_o_to_so by disabling unload_units.
882 CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to keep .o files");
883
884 if (success) {
885 void *handle = dlopen(so_file, RTLD_NOW);
886 if (handle == NULL) {
887 mjit_warning("failure in loading code from compacted '%s': %s", so_file, dlerror());
888 free(unit);
889 return;
890 }
891 unit->handle = handle;
892
893 // lazily dlclose handle (and .so file for win32) on `mjit_finish()`.
894 add_to_list(unit, &compact_units);
895
897 remove_so_file(so_file, unit);
898
899 CRITICAL_SECTION_START(3, "in compact_all_jit_code to read list");
900 list_for_each(&active_units.head, cur, unode) {
901 void *func;
902 char funcname[35]; // TODO: reconsider `35`
903 sprintf(funcname, "_mjit%d", cur->id);
904
905 if ((func = dlsym(handle, funcname)) == NULL) {
906 mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror());
907 continue;
908 }
909
910 if (cur->iseq) { // Check whether GCed or not
911 // Usage of jit_code might be not in a critical section.
913 }
914 }
915 CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to read list");
916 verbose(1, "JIT compaction (%.1fms): Compacted %d methods -> %s", end_time - start_time, active_units.length, so_file);
917 }
918 else {
919 free(unit);
920 verbose(1, "JIT compaction failure (%.1fms): Failed to compact methods", end_time - start_time);
921 }
922# endif // _WIN32
923}
924
925#endif // _MSC_VER
926
927static void *
928load_func_from_so(const char *so_file, const char *funcname, struct rb_mjit_unit *unit)
929{
930 void *handle, *func;
931
932 handle = dlopen(so_file, RTLD_NOW);
933 if (handle == NULL) {
934 mjit_warning("failure in loading code from '%s': %s", so_file, dlerror());
935 return (void *)NOT_ADDED_JIT_ISEQ_FUNC;
936 }
937
938 func = dlsym(handle, funcname);
939 unit->handle = handle;
940 return func;
941}
942
943#ifndef __clang__
944static const char *
945header_name_end(const char *s)
946{
947 const char *e = s + strlen(s);
948# ifdef __GNUC__ // don't chomp .pch for mswin
949 static const char suffix[] = ".gch";
950
951 // chomp .gch suffix
952 if (e > s+sizeof(suffix)-1 && strcmp(e-sizeof(suffix)+1, suffix) == 0) {
953 e -= sizeof(suffix)-1;
954 }
955# endif
956 return e;
957}
958#endif
959
960// Print platform-specific prerequisites in generated code.
961static void
962compile_prelude(FILE *f)
963{
964#ifndef __clang__ // -include-pch is used for Clang
965 const char *s = pch_file;
966 const char *e = header_name_end(s);
967
968 fprintf(f, "#include \"");
969 // print pch_file except .gch for gcc, but keep .pch for mswin
970 for (; s < e; s++) {
971 switch(*s) {
972 case '\\': case '"':
973 fputc('\\', f);
974 }
975 fputc(*s, f);
976 }
977 fprintf(f, "\"\n");
978#endif
979
980#ifdef _WIN32
981 fprintf(f, "void _pei386_runtime_relocator(void){}\n");
982 fprintf(f, "int __stdcall DllMainCRTStartup(void* hinstDLL, unsigned int fdwReason, void* lpvReserved) { return 1; }\n");
983#endif
984}
985
986// Compile ISeq in UNIT and return function pointer of JIT-ed code.
987// It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong.
988static mjit_func_t
989convert_unit_to_func(struct rb_mjit_unit *unit)
990{
991 char c_file_buff[MAXPATHLEN], *c_file = c_file_buff, *so_file, funcname[35]; // TODO: reconsider `35`
992 int fd;
993 FILE *f;
994 void *func;
995 double start_time, end_time;
996 int c_file_len = (int)sizeof(c_file_buff);
997 static const char c_ext[] = ".c";
998 static const char so_ext[] = DLEXT;
999 const int access_mode =
1000#ifdef O_BINARY
1001 O_BINARY|
1002#endif
1003 O_WRONLY|O_EXCL|O_CREAT;
1004#ifndef _MSC_VER
1005 static const char o_ext[] = ".o";
1006 char *o_file;
1007#endif
1008
1009 c_file_len = sprint_uniq_filename(c_file_buff, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1010 if (c_file_len >= (int)sizeof(c_file_buff)) {
1011 ++c_file_len;
1012 c_file = alloca(c_file_len);
1013 c_file_len = sprint_uniq_filename(c_file, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1014 }
1015 ++c_file_len;
1016
1017#ifndef _MSC_VER
1018 o_file = alloca(c_file_len - sizeof(c_ext) + sizeof(o_ext));
1019 memcpy(o_file, c_file, c_file_len - sizeof(c_ext));
1020 memcpy(&o_file[c_file_len - sizeof(c_ext)], o_ext, sizeof(o_ext));
1021#endif
1022 so_file = alloca(c_file_len - sizeof(c_ext) + sizeof(so_ext));
1023 memcpy(so_file, c_file, c_file_len - sizeof(c_ext));
1024 memcpy(&so_file[c_file_len - sizeof(c_ext)], so_ext, sizeof(so_ext));
1025
1026 sprintf(funcname, "_mjit%d", unit->id);
1027
1028 fd = rb_cloexec_open(c_file, access_mode, 0600);
1029 if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
1030 int e = errno;
1031 if (fd >= 0) (void)close(fd);
1032 verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
1034 }
1035
1036 // print #include of MJIT header, etc.
1037 compile_prelude(f);
1038
1039 // wait until mjit_gc_exit_hook is called
1040 CRITICAL_SECTION_START(3, "before mjit_compile to wait GC finish");
1041 while (in_gc) {
1042 verbose(3, "Waiting wakeup from GC");
1043 rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1044 }
1045
1046 // We need to check again here because we could've waited on GC above
1047 if (unit->iseq == NULL) {
1048 fclose(f);
1049 if (!mjit_opts.save_temps)
1050 remove_file(c_file);
1051 in_jit = false; // just being explicit for return
1052 }
1053 else {
1054 in_jit = true;
1055 }
1056 CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish");
1057 if (!in_jit) {
1059 }
1060
1061 // To make MJIT worker thread-safe against GC.compact, copy ISeq values while `in_jit` is true.
1062 long iseq_lineno = 0;
1063 if (FIXNUM_P(unit->iseq->body->location.first_lineno))
1064 // FIX2INT may fallback to rb_num2long(), which is a method call and dangerous in MJIT worker. So using only FIX2LONG.
1065 iseq_lineno = FIX2LONG(unit->iseq->body->location.first_lineno);
1066 char *iseq_label = alloca(RSTRING_LEN(unit->iseq->body->location.label) + 1);
1067 char *iseq_path = alloca(RSTRING_LEN(rb_iseq_path(unit->iseq)) + 1);
1068 strcpy(iseq_label, RSTRING_PTR(unit->iseq->body->location.label));
1069 strcpy(iseq_path, RSTRING_PTR(rb_iseq_path(unit->iseq)));
1070
1071 verbose(2, "start compilation: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1072 fprintf(f, "/* %s@%s:%ld */\n\n", iseq_label, iseq_path, iseq_lineno);
1073 bool success = mjit_compile(f, unit->iseq, funcname);
1074
1075 // release blocking mjit_gc_start_hook
1076 CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC");
1077 in_jit = false;
1078 verbose(3, "Sending wakeup signal to client in a mjit-worker for GC");
1079 rb_native_cond_signal(&mjit_client_wakeup);
1080 CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC");
1081
1082 fclose(f);
1083 if (!success) {
1084 if (!mjit_opts.save_temps)
1085 remove_file(c_file);
1086 verbose(1, "JIT failure: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1088 }
1089
1090 start_time = real_ms_time();
1091#ifdef _MSC_VER
1092 success = compile_c_to_so(c_file, so_file);
1093#else
1094 // splitting .c -> .o step and .o -> .so step, to cache .o files in the future
1095 if ((success = compile_c_to_o(c_file, o_file)) != false) {
1096 success = link_o_to_so((const char *[]){ o_file, NULL }, so_file);
1097
1098 // Always set o_file for compaction. The value is also used for lazy deletion.
1099 unit->o_file = strdup(o_file);
1100 if (unit->o_file == NULL) {
1101 mjit_warning("failed to allocate memory to remember '%s' (%s), removing it...", o_file, strerror(errno));
1102 remove_file(o_file);
1103 }
1104 }
1105#endif
1106 end_time = real_ms_time();
1107
1108 if (!mjit_opts.save_temps)
1109 remove_file(c_file);
1110 if (!success) {
1111 verbose(2, "Failed to generate so: %s", so_file);
1113 }
1114
1115 func = load_func_from_so(so_file, funcname, unit);
1116 if (!mjit_opts.save_temps)
1117 remove_so_file(so_file, unit);
1118
1119 if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1120 verbose(1, "JIT success (%.1fms): %s@%s:%ld -> %s",
1121 end_time - start_time, iseq_label, iseq_path, iseq_lineno, c_file);
1122 }
1123 return (mjit_func_t)func;
1124}
1125
1126typedef struct {
1132
1133// Singleton MJIT copy job. This is made global since it needs to be durable even when MJIT worker thread is stopped.
1134// (ex: register job -> MJIT pause -> MJIT resume -> dispatch job. Actually this should be just cancelled by finish_p check)
1135static mjit_copy_job_t mjit_copy_job = { .iseq = NULL, .finish_p = true };
1136
1137static void mjit_copy_job_handler(void *data);
1138
1139// vm_trace.c
1140int rb_workqueue_register(unsigned flags, rb_postponed_job_func_t , void *);
1141
1142// Copy inline cache values of `iseq` to `cc_entries` and `is_entries`.
1143// These buffers should be pre-allocated properly prior to calling this function.
1144// Return true if copy succeeds or is not needed.
1145//
1146// We're lazily copying cache values from main thread because these cache values
1147// could be different between ones on enqueue timing and ones on dequeue timing.
1148bool
1150{
1151 mjit_copy_job_t *job = &mjit_copy_job; // just a short hand
1152
1153 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1154 job->finish_p = true; // disable dispatching this job in mjit_copy_job_handler while it's being modified
1155 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1156
1157 job->cc_entries = cc_entries;
1158 job->is_entries = is_entries;
1159
1160 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1161 job->iseq = iseq; // Prevernt GC of this ISeq from here
1162 VM_ASSERT(in_jit);
1163 in_jit = false; // To avoid deadlock, allow running GC while waiting for copy job
1164 rb_native_cond_signal(&mjit_client_wakeup); // Unblock main thread waiting in `mjit_gc_start_hook`
1165
1166 job->finish_p = false; // allow dispatching this job in mjit_copy_job_handler
1167 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1168
1169 if (UNLIKELY(mjit_opts.wait)) {
1170 mjit_copy_job_handler((void *)job);
1171 }
1172 else if (rb_workqueue_register(0, mjit_copy_job_handler, (void *)job)) {
1173 CRITICAL_SECTION_START(3, "in MJIT copy job wait");
1174 // checking `stop_worker_p` too because `RUBY_VM_CHECK_INTS(ec)` may not
1175 // lush mjit_copy_job_handler when EC_EXEC_TAG() is not TAG_NONE, and then
1176 // `stop_worker()` could dead lock with this function.
1177 while (!job->finish_p && !stop_worker_p) {
1178 rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1179 verbose(3, "Getting wakeup from client");
1180 }
1181 CRITICAL_SECTION_FINISH(3, "in MJIT copy job wait");
1182 }
1183
1184 CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1185 bool success_p = job->finish_p;
1186 // Disable dispatching this job in mjit_copy_job_handler while memory allocated by alloca
1187 // could be expired after finishing this function.
1188 job->finish_p = true;
1189
1190 in_jit = true; // Prohibit GC during JIT compilation
1191 if (job->iseq == NULL) // ISeq GC is notified in mjit_mark_iseq
1192 success_p = false;
1193 job->iseq = NULL; // Allow future GC of this ISeq from here
1194 CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1195 return success_p;
1196}
1197
1198// The function implementing a worker. It is executed in a separate
1199// thread by rb_thread_create_mjit_thread. It compiles precompiled header
1200// and then compiles requested ISeqs.
1201void
1203{
1204#ifndef _MSC_VER
1205 if (pch_status == PCH_NOT_READY) {
1206 make_pch();
1207 }
1208#endif
1209 if (pch_status == PCH_FAILED) {
1210 mjit_enabled = false;
1211 CRITICAL_SECTION_START(3, "in worker to update worker_stopped");
1212 worker_stopped = true;
1213 verbose(3, "Sending wakeup signal to client in a mjit-worker");
1214 rb_native_cond_signal(&mjit_client_wakeup);
1215 CRITICAL_SECTION_FINISH(3, "in worker to update worker_stopped");
1216 return; // TODO: do the same thing in the latter half of mjit_finish
1217 }
1218
1219 // main worker loop
1220 while (!stop_worker_p) {
1221 struct rb_mjit_unit *unit;
1222
1223 // wait until unit is available
1224 CRITICAL_SECTION_START(3, "in worker dequeue");
1225 while ((list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) {
1226 rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1227 verbose(3, "Getting wakeup from client");
1228 }
1229 unit = get_from_list(&unit_queue);
1230 CRITICAL_SECTION_FINISH(3, "in worker dequeue");
1231
1232 if (unit) {
1233 // JIT compile
1234 mjit_func_t func = convert_unit_to_func(unit);
1235 (void)RB_DEBUG_COUNTER_INC_IF(mjit_compile_failures, func == (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC);
1236
1237 CRITICAL_SECTION_START(3, "in jit func replace");
1238 while (in_gc) { // Make sure we're not GC-ing when touching ISeq
1239 verbose(3, "Waiting wakeup from GC");
1240 rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1241 }
1242 if (unit->iseq) { // Check whether GCed or not
1243 if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1244 add_to_list(unit, &active_units);
1245 }
1246 // Usage of jit_code might be not in a critical section.
1247 MJIT_ATOMIC_SET(unit->iseq->body->jit_func, func);
1248 }
1249 else {
1250 free_unit(unit);
1251 }
1252 CRITICAL_SECTION_FINISH(3, "in jit func replace");
1253
1254#ifndef _MSC_VER
1255 // Combine .o files to one .so and reload all jit_func to improve memory locality
1256 if ((!mjit_opts.wait && unit_queue.length == 0 && active_units.length > 1)
1257 || active_units.length == mjit_opts.max_cache_size) {
1258 compact_all_jit_code();
1259 }
1260#endif
1261 }
1262 }
1263
1264 // To keep mutex unlocked when it is destroyed by mjit_finish, don't wrap CRITICAL_SECTION here.
1265 worker_stopped = true;
1266}
#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
void mjit_worker(void)
Definition: mjit_worker.c:1202
verbose(int level, const char *format,...)
Definition: mjit_worker.c:303
@ PCH_SUCCESS
Definition: mjit_worker.c:232
@ PCH_FAILED
Definition: mjit_worker.c:232
@ PCH_NOT_READY
Definition: mjit_worker.c:232
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:1149
#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[])
#define StringValuePtr(v)
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)
#define stderr
void * realloc(void *, size_t) __attribute__((__warn_unused_result__)) __attribute__((__alloc_size__(2)))
__pid_t pid_t
int fclose(FILE *)
() void(cc->call !=vm_call_general)
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:6450
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)
unsigned long VALUE
Definition: ruby.h:102
#define f
#define const
Definition: strftime.c:103
struct rb_call_cache * cc_entries
Definition: mjit_worker.c:1128
union iseq_inline_storage_entry * is_entries
Definition: mjit_worker.c:1129
const rb_iseq_t * iseq
Definition: mjit_worker.c:1127
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