Ruby  2.7.0p0(2019-12-25revision647ee6f091eafcce70ffb75ddf7e121e192ab217)
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)
117 typedef 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.
126 struct rb_mjit_unit {
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.
146  struct list_node unode;
147  // mjit_compile's optimization switches
149 };
150 
151 // Linked list of struct rb_mjit_unit.
153  struct list_head head;
154  int length; // the list length
155 };
156 
161 
167 
168 // process.c
169 extern 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.
177 bool 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.
180 bool 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.
184 static struct rb_mjit_unit_list unit_queue = { LIST_HEAD_INIT(unit_queue.head) };
185 // List of units which are successfully compiled.
186 static 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()`.
188 static struct rb_mjit_unit_list compact_units = { LIST_HEAD_INIT(compact_units.head) };
189 // List of units before recompilation and just waiting for dlclose().
190 static 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.
192 static int current_unit_num;
193 // A mutex for conitionals and critical sections.
194 static rb_nativethread_lock_t mjit_engine_mutex;
195 // A thread conditional to wake up `mjit_finish` at the end of PCH thread.
196 static 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.
199 static 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.
202 static rb_nativethread_cond_t mjit_worker_wakeup;
203 // A thread conditional to wake up workers if at the end of GC.
204 static rb_nativethread_cond_t mjit_gc_wakeup;
205 // True when GC is working.
206 static bool in_gc;
207 // True when JIT is working.
208 static bool in_jit;
209 // Set to true to stop worker.
210 static bool stop_worker_p;
211 // Set to true if worker is stopped.
212 static bool worker_stopped;
213 
214 // Path of "/tmp", which can be changed to $TMP in MinGW.
215 static 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.
218 static VALUE valid_class_serials;
219 
220 // Used C compiler path.
221 static const char *cc_path;
222 // Used C compiler flags.
223 static const char **cc_common_args;
224 // Used C compiler flags added by --jit-debug=...
225 static char **cc_added_args;
226 // Name of the precompiled header file.
227 static char *pch_file;
228 // The process id which should delete the pch_file on mjit_finish.
229 static 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.
232 static enum {PCH_NOT_READY, PCH_FAILED, PCH_SUCCESS} pch_status;
233 
234 #ifndef _MSC_VER
235 // Name of the header file.
236 static char *header_file;
237 #endif
238 
239 #ifdef _WIN32
240 // Linker option to enable libruby.
241 static 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 
264 static const char *const CC_COMMON_ARGS[] = {
266  NULL
267 };
268 
269 static const char *const CC_DEBUG_ARGS[] = {MJIT_DEBUGFLAGS NULL};
270 static const char *const CC_OPTIMIZE_ARGS[] = {MJIT_OPTFLAGS NULL};
271 
272 static const char *const CC_LDSHARED_ARGS[] = {MJIT_LDSHARED GCC_PIC_FLAGS NULL};
273 static const char *const CC_DLDFLAGS_ARGS[] = {MJIT_DLDFLAGS NULL};
274 // `CC_LINKER_ARGS` are linker flags which must be passed to `-c` as well.
275 static const char *const CC_LINKER_ARGS[] = {
276 #if defined __GNUC__ && !defined __clang__ && !defined(__OpenBSD__)
277  "-nostartfiles",
278 #endif
280 };
281 
282 static 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.
302 PRINTF_ARGS(static void, 2, 3)
303 verbose(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 
321 PRINTF_ARGS(static void, 1, 2)
322 mjit_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.
337 static void
338 add_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 
349 static void
350 remove_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 
363 static void
364 remove_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.
372 static void
373 clean_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.
382  if (!mjit_opts.save_temps && !unit->o_file_inherited_p)
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.
408 static void
409 free_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`.
423 static inline void
424 CRITICAL_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`.
433 static inline void
434 CRITICAL_SECTION_FINISH(int level, const char *msg)
435 {
436  verbose(level, "Unlocked %s", msg);
437  rb_native_mutex_unlock(&mjit_engine_mutex);
438 }
439 
440 static int
441 sprint_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__
448 double ruby_real_ms_time(void);
449 # define real_ms_time() ruby_real_ms_time()
450 #else
451 static double
452 real_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.
474 bool
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.
486 static struct rb_mjit_unit *
487 get_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.
510 static size_t
511 args_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.
522 static char **
523 form_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__
550 COMPILER_WARNING_IGNORED(-Wdeprecated-declarations)
551 #endif
552 // Start an OS process of absolute executable path with arguments `argv`.
553 // Return PID of the process.
554 static pid_t
555 start_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
615 static int
616 exec_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) {
652  rb_native_cond_destroy(&cond);
653  }
654  return exit_code;
655 }
656 
657 static void
658 remove_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)
676 static bool
677 compile_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.
749 static void
750 make_pch(void)
751 {
752  const char *rest_args[] = {
753 # ifdef __clang__
754  "-emit-pch",
755 # endif
756  // -nodefaultlibs is a linker flag, but it may affect cc1 behavior on Gentoo, which should NOT be changed on pch:
757  // https://gitweb.gentoo.org/proj/gcc-patches.git/tree/7.3.0/gentoo/13_all_default-ssp-fix.patch
759  "-o", pch_file, header_file,
760  NULL,
761  };
762 
763  verbose(2, "Creating precompiled header");
764  char **args = form_args(4, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, rest_args);
765  if (args == NULL) {
766  mjit_warning("making precompiled header failed on forming args");
767  CRITICAL_SECTION_START(3, "in make_pch");
768  pch_status = PCH_FAILED;
769  CRITICAL_SECTION_FINISH(3, "in make_pch");
770  return;
771  }
772 
773  int exit_code = exec_process(cc_path, args);
774  free(args);
775 
776  CRITICAL_SECTION_START(3, "in make_pch");
777  if (exit_code == 0) {
778  pch_status = PCH_SUCCESS;
779  }
780  else {
781  mjit_warning("Making precompiled header failed on compilation. Stopping MJIT worker...");
782  pch_status = PCH_FAILED;
783  }
784  /* wakeup `mjit_finish` */
785  rb_native_cond_broadcast(&mjit_pch_wakeup);
786  CRITICAL_SECTION_FINISH(3, "in make_pch");
787 }
788 
789 // Compile .c file to .o file. It returns true if it succeeds. (non-mswin)
790 static bool
791 compile_c_to_o(const char *c_file, const char *o_file)
792 {
793  const char *files[] = {
794  "-o", o_file, c_file,
795 # ifdef __clang__
796  "-include-pch", pch_file,
797 # endif
798  "-c", NULL
799  };
800 
801  char **args = form_args(5, cc_common_args, CC_CODEFLAG_ARGS, cc_added_args, files, CC_LINKER_ARGS);
802  if (args == NULL)
803  return false;
804 
805  int exit_code = exec_process(cc_path, args);
806  free(args);
807 
808  if (exit_code != 0)
809  verbose(2, "compile_c_to_o: compile error: %d", exit_code);
810  return exit_code == 0;
811 }
812 
813 // Link .o files to .so file. It returns true if it succeeds. (non-mswin)
814 static bool
815 link_o_to_so(const char **o_files, const char *so_file)
816 {
817  const char *options[] = {
818  "-o", so_file,
819 # ifdef _WIN32
820  libruby_pathflag,
821 # endif
822  NULL
823  };
824 
825  char **args = form_args(7, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
826  options, o_files, CC_LIBS, CC_DLDFLAGS_ARGS, CC_LINKER_ARGS);
827  if (args == NULL)
828  return false;
829 
830  int exit_code = exec_process(cc_path, args);
831  free(args);
832 
833  if (exit_code != 0)
834  verbose(2, "link_o_to_so: link error: %d", exit_code);
835  return exit_code == 0;
836 }
837 
838 // Link all cached .o files and build a .so file. Reload all JIT func from it. This
839 // allows to avoid JIT code fragmentation and improve performance to call JIT-ed code.
840 static void
841 compact_all_jit_code(void)
842 {
843 # ifndef _WIN32 // This requires header transformation but we don't transform header on Windows for now
844  struct rb_mjit_unit *unit, *cur = 0;
845  double start_time, end_time;
846  static const char so_ext[] = DLEXT;
847  char so_file[MAXPATHLEN];
848  const char **o_files;
849  int i = 0;
850 
851  // Abnormal use case of rb_mjit_unit that doesn't have ISeq
852  unit = calloc(1, sizeof(struct rb_mjit_unit)); // To prevent GC, don't use ZALLOC
853  if (unit == NULL) return;
854  unit->id = current_unit_num++;
855  sprint_uniq_filename(so_file, (int)sizeof(so_file), unit->id, MJIT_TMP_PREFIX, so_ext);
856 
857  // NULL-ending for form_args
858  o_files = alloca(sizeof(char *) * (active_units.length + 1));
859  o_files[active_units.length] = NULL;
860  CRITICAL_SECTION_START(3, "in compact_all_jit_code to keep .o files");
861  list_for_each(&active_units.head, cur, unode) {
862  o_files[i] = cur->o_file;
863  i++;
864  }
865 
866  start_time = real_ms_time();
867  bool success = link_o_to_so(o_files, so_file);
868  end_time = real_ms_time();
869 
870  // TODO: Shrink this big critical section. For now, this is needed to prevent failure by missing .o files.
871  // This assumes that o -> so link doesn't take long time because the bottleneck, which is compiler optimization,
872  // is already done. But actually it takes about 500ms for 5,000 methods on my Linux machine, so it's better to
873  // finish this critical section before link_o_to_so by disabling unload_units.
874  CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to keep .o files");
875 
876  if (success) {
877  void *handle = dlopen(so_file, RTLD_NOW);
878  if (handle == NULL) {
879  mjit_warning("failure in loading code from compacted '%s': %s", so_file, dlerror());
880  free(unit);
881  return;
882  }
883  unit->handle = handle;
884 
885  // lazily dlclose handle (and .so file for win32) on `mjit_finish()`.
886  add_to_list(unit, &compact_units);
887 
888  if (!mjit_opts.save_temps)
889  remove_so_file(so_file, unit);
890 
891  CRITICAL_SECTION_START(3, "in compact_all_jit_code to read list");
892  list_for_each(&active_units.head, cur, unode) {
893  void *func;
894  char funcname[35]; // TODO: reconsider `35`
895  sprintf(funcname, "_mjit%d", cur->id);
896 
897  if ((func = dlsym(handle, funcname)) == NULL) {
898  mjit_warning("skipping to reload '%s' from '%s': %s", funcname, so_file, dlerror());
899  continue;
900  }
901 
902  if (cur->iseq) { // Check whether GCed or not
903  // Usage of jit_code might be not in a critical section.
905  }
906  }
907  CRITICAL_SECTION_FINISH(3, "in compact_all_jit_code to read list");
908  verbose(1, "JIT compaction (%.1fms): Compacted %d methods -> %s", end_time - start_time, active_units.length, so_file);
909  }
910  else {
911  free(unit);
912  verbose(1, "JIT compaction failure (%.1fms): Failed to compact methods", end_time - start_time);
913  }
914 # endif // _WIN32
915 }
916 
917 #endif // _MSC_VER
918 
919 static void *
920 load_func_from_so(const char *so_file, const char *funcname, struct rb_mjit_unit *unit)
921 {
922  void *handle, *func;
923 
924  handle = dlopen(so_file, RTLD_NOW);
925  if (handle == NULL) {
926  mjit_warning("failure in loading code from '%s': %s", so_file, dlerror());
927  return (void *)NOT_ADDED_JIT_ISEQ_FUNC;
928  }
929 
930  func = dlsym(handle, funcname);
931  unit->handle = handle;
932  return func;
933 }
934 
935 #ifndef __clang__
936 static const char *
937 header_name_end(const char *s)
938 {
939  const char *e = s + strlen(s);
940 # ifdef __GNUC__ // don't chomp .pch for mswin
941  static const char suffix[] = ".gch";
942 
943  // chomp .gch suffix
944  if (e > s+sizeof(suffix)-1 && strcmp(e-sizeof(suffix)+1, suffix) == 0) {
945  e -= sizeof(suffix)-1;
946  }
947 # endif
948  return e;
949 }
950 #endif
951 
952 // Print platform-specific prerequisites in generated code.
953 static void
954 compile_prelude(FILE *f)
955 {
956 #ifndef __clang__ // -include-pch is used for Clang
957  const char *s = pch_file;
958  const char *e = header_name_end(s);
959 
960  fprintf(f, "#include \"");
961  // print pch_file except .gch for gcc, but keep .pch for mswin
962  for (; s < e; s++) {
963  switch(*s) {
964  case '\\': case '"':
965  fputc('\\', f);
966  }
967  fputc(*s, f);
968  }
969  fprintf(f, "\"\n");
970 #endif
971 
972 #ifdef _WIN32
973  fprintf(f, "void _pei386_runtime_relocator(void){}\n");
974  fprintf(f, "int __stdcall DllMainCRTStartup(void* hinstDLL, unsigned int fdwReason, void* lpvReserved) { return 1; }\n");
975 #endif
976 }
977 
978 // Compile ISeq in UNIT and return function pointer of JIT-ed code.
979 // It may return NOT_COMPILED_JIT_ISEQ_FUNC if something went wrong.
980 static mjit_func_t
981 convert_unit_to_func(struct rb_mjit_unit *unit)
982 {
983  char c_file_buff[MAXPATHLEN], *c_file = c_file_buff, *so_file, funcname[35]; // TODO: reconsider `35`
984  int fd;
985  FILE *f;
986  void *func;
987  double start_time, end_time;
988  int c_file_len = (int)sizeof(c_file_buff);
989  static const char c_ext[] = ".c";
990  static const char so_ext[] = DLEXT;
991  const int access_mode =
992 #ifdef O_BINARY
993  O_BINARY|
994 #endif
995  O_WRONLY|O_EXCL|O_CREAT;
996 #ifndef _MSC_VER
997  static const char o_ext[] = ".o";
998  char *o_file;
999 #endif
1000 
1001  c_file_len = sprint_uniq_filename(c_file_buff, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1002  if (c_file_len >= (int)sizeof(c_file_buff)) {
1003  ++c_file_len;
1004  c_file = alloca(c_file_len);
1005  c_file_len = sprint_uniq_filename(c_file, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
1006  }
1007  ++c_file_len;
1008 
1009 #ifndef _MSC_VER
1010  o_file = alloca(c_file_len - sizeof(c_ext) + sizeof(o_ext));
1011  memcpy(o_file, c_file, c_file_len - sizeof(c_ext));
1012  memcpy(&o_file[c_file_len - sizeof(c_ext)], o_ext, sizeof(o_ext));
1013 #endif
1014  so_file = alloca(c_file_len - sizeof(c_ext) + sizeof(so_ext));
1015  memcpy(so_file, c_file, c_file_len - sizeof(c_ext));
1016  memcpy(&so_file[c_file_len - sizeof(c_ext)], so_ext, sizeof(so_ext));
1017 
1018  sprintf(funcname, "_mjit%d", unit->id);
1019 
1020  fd = rb_cloexec_open(c_file, access_mode, 0600);
1021  if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
1022  int e = errno;
1023  if (fd >= 0) (void)close(fd);
1024  verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", c_file, strerror(e));
1026  }
1027 
1028  // print #include of MJIT header, etc.
1029  compile_prelude(f);
1030 
1031  // wait until mjit_gc_exit_hook is called
1032  CRITICAL_SECTION_START(3, "before mjit_compile to wait GC finish");
1033  while (in_gc) {
1034  verbose(3, "Waiting wakeup from GC");
1035  rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1036  }
1037 
1038  // We need to check again here because we could've waited on GC above
1039  if (unit->iseq == NULL) {
1040  fclose(f);
1041  if (!mjit_opts.save_temps)
1042  remove_file(c_file);
1043  in_jit = false; // just being explicit for return
1044  }
1045  else {
1046  in_jit = true;
1047  }
1048  CRITICAL_SECTION_FINISH(3, "before mjit_compile to wait GC finish");
1049  if (!in_jit) {
1051  }
1052 
1053  // To make MJIT worker thread-safe against GC.compact, copy ISeq values while `in_jit` is true.
1054  long iseq_lineno = 0;
1055  if (FIXNUM_P(unit->iseq->body->location.first_lineno))
1056  // FIX2INT may fallback to rb_num2long(), which is a method call and dangerous in MJIT worker. So using only FIX2LONG.
1057  iseq_lineno = FIX2LONG(unit->iseq->body->location.first_lineno);
1058  char *iseq_label = alloca(RSTRING_LEN(unit->iseq->body->location.label) + 1);
1059  char *iseq_path = alloca(RSTRING_LEN(rb_iseq_path(unit->iseq)) + 1);
1060  strcpy(iseq_label, RSTRING_PTR(unit->iseq->body->location.label));
1061  strcpy(iseq_path, RSTRING_PTR(rb_iseq_path(unit->iseq)));
1062 
1063  verbose(2, "start compilation: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1064  fprintf(f, "/* %s@%s:%ld */\n\n", iseq_label, iseq_path, iseq_lineno);
1065  bool success = mjit_compile(f, unit->iseq, funcname);
1066 
1067  // release blocking mjit_gc_start_hook
1068  CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC");
1069  in_jit = false;
1070  verbose(3, "Sending wakeup signal to client in a mjit-worker for GC");
1071  rb_native_cond_signal(&mjit_client_wakeup);
1072  CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC");
1073 
1074  fclose(f);
1075  if (!success) {
1076  if (!mjit_opts.save_temps)
1077  remove_file(c_file);
1078  verbose(1, "JIT failure: %s@%s:%ld -> %s", iseq_label, iseq_path, iseq_lineno, c_file);
1080  }
1081 
1082  start_time = real_ms_time();
1083 #ifdef _MSC_VER
1084  success = compile_c_to_so(c_file, so_file);
1085 #else
1086  // splitting .c -> .o step and .o -> .so step, to cache .o files in the future
1087  if ((success = compile_c_to_o(c_file, o_file)) != false) {
1088  success = link_o_to_so((const char *[]){ o_file, NULL }, so_file);
1089 
1090  // Always set o_file for compaction. The value is also used for lazy deletion.
1091  unit->o_file = strdup(o_file);
1092  if (unit->o_file == NULL) {
1093  mjit_warning("failed to allocate memory to remember '%s' (%s), removing it...", o_file, strerror(errno));
1094  remove_file(o_file);
1095  }
1096  }
1097 #endif
1098  end_time = real_ms_time();
1099 
1100  if (!mjit_opts.save_temps)
1101  remove_file(c_file);
1102  if (!success) {
1103  verbose(2, "Failed to generate so: %s", so_file);
1105  }
1106 
1107  func = load_func_from_so(so_file, funcname, unit);
1108  if (!mjit_opts.save_temps)
1109  remove_so_file(so_file, unit);
1110 
1111  if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1112  verbose(1, "JIT success (%.1fms): %s@%s:%ld -> %s",
1113  end_time - start_time, iseq_label, iseq_path, iseq_lineno, c_file);
1114  }
1115  return (mjit_func_t)func;
1116 }
1117 
1118 typedef struct {
1119  const rb_iseq_t *iseq;
1122  bool finish_p;
1123 } mjit_copy_job_t;
1124 
1125 // Singleton MJIT copy job. This is made global since it needs to be durable even when MJIT worker thread is stopped.
1126 // (ex: register job -> MJIT pause -> MJIT resume -> dispatch job. Actually this should be just cancelled by finish_p check)
1127 static mjit_copy_job_t mjit_copy_job = { .iseq = NULL, .finish_p = true };
1128 
1129 static void mjit_copy_job_handler(void *data);
1130 
1131 // vm_trace.c
1132 int rb_workqueue_register(unsigned flags, rb_postponed_job_func_t , void *);
1133 
1134 // Copy inline cache values of `iseq` to `cc_entries` and `is_entries`.
1135 // These buffers should be pre-allocated properly prior to calling this function.
1136 // Return true if copy succeeds or is not needed.
1137 //
1138 // We're lazily copying cache values from main thread because these cache values
1139 // could be different between ones on enqueue timing and ones on dequeue timing.
1140 bool
1142 {
1143  mjit_copy_job_t *job = &mjit_copy_job; // just a short hand
1144 
1145  CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1146  job->finish_p = true; // disable dispatching this job in mjit_copy_job_handler while it's being modified
1147  CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1148 
1149  job->cc_entries = cc_entries;
1150  job->is_entries = is_entries;
1151 
1152  CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1153  job->iseq = iseq; // Prevernt GC of this ISeq from here
1154  VM_ASSERT(in_jit);
1155  in_jit = false; // To avoid deadlock, allow running GC while waiting for copy job
1156  rb_native_cond_signal(&mjit_client_wakeup); // Unblock main thread waiting in `mjit_gc_start_hook`
1157 
1158  job->finish_p = false; // allow dispatching this job in mjit_copy_job_handler
1159  CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1160 
1161  if (UNLIKELY(mjit_opts.wait)) {
1162  mjit_copy_job_handler((void *)job);
1163  }
1164  else if (rb_workqueue_register(0, mjit_copy_job_handler, (void *)job)) {
1165  CRITICAL_SECTION_START(3, "in MJIT copy job wait");
1166  // checking `stop_worker_p` too because `RUBY_VM_CHECK_INTS(ec)` may not
1167  // lush mjit_copy_job_handler when EC_EXEC_TAG() is not TAG_NONE, and then
1168  // `stop_worker()` could dead lock with this function.
1169  while (!job->finish_p && !stop_worker_p) {
1170  rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1171  verbose(3, "Getting wakeup from client");
1172  }
1173  CRITICAL_SECTION_FINISH(3, "in MJIT copy job wait");
1174  }
1175 
1176  CRITICAL_SECTION_START(3, "in mjit_copy_cache_from_main_thread");
1177  bool success_p = job->finish_p;
1178  // Disable dispatching this job in mjit_copy_job_handler while memory allocated by alloca
1179  // could be expired after finishing this function.
1180  job->finish_p = true;
1181 
1182  in_jit = true; // Prohibit GC during JIT compilation
1183  if (job->iseq == NULL) // ISeq GC is notified in mjit_mark_iseq
1184  success_p = false;
1185  job->iseq = NULL; // Allow future GC of this ISeq from here
1186  CRITICAL_SECTION_FINISH(3, "in mjit_copy_cache_from_main_thread");
1187  return success_p;
1188 }
1189 
1190 // The function implementing a worker. It is executed in a separate
1191 // thread by rb_thread_create_mjit_thread. It compiles precompiled header
1192 // and then compiles requested ISeqs.
1193 void
1195 {
1196 #ifndef _MSC_VER
1197  if (pch_status == PCH_NOT_READY) {
1198  make_pch();
1199  }
1200 #endif
1201  if (pch_status == PCH_FAILED) {
1202  mjit_enabled = false;
1203  CRITICAL_SECTION_START(3, "in worker to update worker_stopped");
1204  worker_stopped = true;
1205  verbose(3, "Sending wakeup signal to client in a mjit-worker");
1206  rb_native_cond_signal(&mjit_client_wakeup);
1207  CRITICAL_SECTION_FINISH(3, "in worker to update worker_stopped");
1208  return; // TODO: do the same thing in the latter half of mjit_finish
1209  }
1210 
1211  // main worker loop
1212  while (!stop_worker_p) {
1213  struct rb_mjit_unit *unit;
1214 
1215  // wait until unit is available
1216  CRITICAL_SECTION_START(3, "in worker dequeue");
1217  while ((list_empty(&unit_queue.head) || active_units.length >= mjit_opts.max_cache_size) && !stop_worker_p) {
1218  rb_native_cond_wait(&mjit_worker_wakeup, &mjit_engine_mutex);
1219  verbose(3, "Getting wakeup from client");
1220  }
1221  unit = get_from_list(&unit_queue);
1222  CRITICAL_SECTION_FINISH(3, "in worker dequeue");
1223 
1224  if (unit) {
1225  // JIT compile
1226  mjit_func_t func = convert_unit_to_func(unit);
1227  (void)RB_DEBUG_COUNTER_INC_IF(mjit_compile_failures, func == (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC);
1228 
1229  CRITICAL_SECTION_START(3, "in jit func replace");
1230  while (in_gc) { // Make sure we're not GC-ing when touching ISeq
1231  verbose(3, "Waiting wakeup from GC");
1232  rb_native_cond_wait(&mjit_gc_wakeup, &mjit_engine_mutex);
1233  }
1234  if (unit->iseq) { // Check whether GCed or not
1235  if ((uintptr_t)func > (uintptr_t)LAST_JIT_ISEQ_FUNC) {
1236  add_to_list(unit, &active_units);
1237  }
1238  // Usage of jit_code might be not in a critical section.
1239  MJIT_ATOMIC_SET(unit->iseq->body->jit_func, func);
1240  }
1241  else {
1242  free_unit(unit);
1243  }
1244  CRITICAL_SECTION_FINISH(3, "in jit func replace");
1245 
1246 #ifndef _MSC_VER
1247  // Combine .o files to one .so and reload all jit_func to improve memory locality
1248  if ((!mjit_opts.wait && unit_queue.length == 0 && active_units.length > 1)
1249  || active_units.length == mjit_opts.max_cache_size) {
1250  compact_all_jit_code();
1251  }
1252 #endif
1253  }
1254  }
1255 
1256  // To keep mutex unlocked when it is destroyed by mjit_finish, don't wrap CRITICAL_SECTION here.
1257  worker_stopped = true;
1258 }
mjit_config.h
mjit_copy_job_t::cc_entries
struct rb_call_cache * cc_entries
Definition: mjit_worker.c:1120
fdopen
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 *)
UNLIKELY
#define UNLIKELY(x)
Definition: ffi_common.h:126
void
void
Definition: rb_mjit_min_header-2.7.0.h:13273
stat
Definition: rb_mjit_min_header-2.7.0.h:2384
_exit
void _exit(int __status) __attribute__((__noreturn__))
rb_mjit_unit::compile_info
struct rb_mjit_compile_info compile_info
Definition: mjit_worker.c:148
RB_DEBUG_COUNTER_INC_IF
#define RB_DEBUG_COUNTER_INC_IF(type, cond)
Definition: debug_counter.h:377
execv
int execv(const char *__path, char *const __argv[])
MJIT_CFLAGS
#define MJIT_CFLAGS
Definition: mjit_config.h:11
rb_iseq_struct
Definition: vm_core.h:456
va_arg
#define va_arg(v, l)
Definition: rb_mjit_min_header-2.7.0.h:3980
MJIT_OPTFLAGS
#define MJIT_OPTFLAGS
Definition: mjit_config.h:12
close
int close(int __fildes)
path
VALUE path
Definition: rb_mjit_min_header-2.7.0.h:7351
rb_mjit_unit
Definition: mjit_worker.c:126
gc.h
PCH_SUCCESS
@ PCH_SUCCESS
Definition: mjit_worker.c:232
vfork
pid_t vfork(void)
rb_iseq_constant_body::jit_unit
struct rb_mjit_unit * jit_unit
Definition: rb_mjit_min_header-2.7.0.h:9595
snprintf
int snprintf(char *__restrict, size_t, const char *__restrict,...) __attribute__((__format__(__printf__
EINTR
#define EINTR
Definition: rb_mjit_min_header-2.7.0.h:10941
stat
int stat(const char *__restrict __path, struct stat *__restrict __sbuf)
RSTRING_PTR
#define RSTRING_PTR(str)
Definition: ruby.h:1009
MJIT_TMP_PREFIX
#define MJIT_TMP_PREFIX
Definition: mjit_worker.c:123
MAXPATHLEN
#define MAXPATHLEN
Definition: mjit_worker.c:103
rb_mjit_unit::unode
struct list_node unode
Definition: mjit_worker.c:146
PRINTF_ARGS
#define PRINTF_ARGS(decl, string_index, first_to_check)
Definition: defines.h:114
VALUE
unsigned long VALUE
Definition: ruby.h:102
GET_VM
#define GET_VM()
Definition: vm_core.h:1764
rb_mjit_unit::id
int id
Definition: mjit_worker.c:128
list_del
#define list_del(n)
Definition: rb_mjit_min_header-2.7.0.h:9041
rb_iseq_constant_body::jit_func
VALUE(* jit_func)(struct rb_execution_context_struct *, struct rb_control_frame_struct *)
Definition: rb_mjit_min_header-2.7.0.h:9592
GCC_PIC_FLAGS
#define GCC_PIC_FLAGS
Definition: mjit_worker.c:252
WAITPID_USE_SIGCHLD
#define WAITPID_USE_SIGCHLD
Definition: vm_core.h:124
RB_DEBUG_COUNTER_mjit_length_active_units
@ RB_DEBUG_COUNTER_mjit_length_active_units
Definition: rb_mjit_min_header-2.7.0.h:11698
ruby_waitpid_locked
rb_pid_t ruby_waitpid_locked(rb_vm_t *, rb_pid_t, int *status, int options, rb_nativethread_cond_t *cond)
Definition: process.c:1069
int
__inline__ int
Definition: rb_mjit_min_header-2.7.0.h:2839
CLOCK_REALTIME
#define CLOCK_REALTIME
Definition: win32.h:133
rb_iseq_path
VALUE rb_iseq_path(const rb_iseq_t *iseq)
Definition: iseq.c:1027
MJIT_CC_COMMON
#define MJIT_CC_COMMON
Definition: mjit_config.h:10
RB_DEBUG_COUNTER_mjit_length_compact_units
@ RB_DEBUG_COUNTER_mjit_length_compact_units
Definition: rb_mjit_min_header-2.7.0.h:11699
rb_iseq_constant_body::location
rb_iseq_location_t location
Definition: vm_core.h:399
append_str2
#define append_str2(p, str, len)
Definition: mjit_worker.c:670
rb_iseq_location_struct::first_lineno
VALUE first_lineno
Definition: vm_core.h:276
mjit_copy_job_t::iseq
const rb_iseq_t * iseq
Definition: mjit_worker.c:1119
mjit_copy_job_t::is_entries
union iseq_inline_storage_entry * is_entries
Definition: mjit_worker.c:1121
MJIT_DEBUGFLAGS
#define MJIT_DEBUGFLAGS
Definition: mjit_config.h:13
NOT_COMPILED_JIT_ISEQ_FUNC
@ NOT_COMPILED_JIT_ISEQ_FUNC
Definition: rb_mjit_min_header-2.7.0.h:11721
rb_postponed_job_func_t
void(* rb_postponed_job_func_t)(void *arg)
Definition: debug.h:91
uintptr_t
unsigned int uintptr_t
Definition: win32.h:106
MJIT_DLDFLAGS
#define MJIT_DLDFLAGS
Definition: mjit_config.h:15
rb_hash_stlike_lookup
MJIT_FUNC_EXPORTED int rb_hash_stlike_lookup(VALUE hash, st_data_t key, st_data_t *pval)
Definition: hash.c:1944
mjit_options::warnings
char warnings
Definition: rb_mjit_min_header-2.7.0.h:11727
NULL
#define NULL
Definition: _sdbm.c:101
mjit_compile
_Bool mjit_compile(FILE *f, const rb_iseq_t *iseq, const char *funcname)
vfprintf
int int int int int int vfprintf(FILE *__restrict, const char *__restrict, __gnuc_va_list) __attribute__((__format__(__printf__
FIX2LONG
#define FIX2LONG(x)
Definition: ruby.h:394
strlen
size_t strlen(const char *)
VM_ASSERT
#define VM_ASSERT(expr)
Definition: vm_core.h:56
rb_mjit_unit::o_file
char * o_file
Definition: mjit_worker.c:134
RB_DEBUG_COUNTER_mjit_length_stale_units
@ RB_DEBUG_COUNTER_mjit_length_stale_units
Definition: rb_mjit_min_header-2.7.0.h:11700
mjit_copy_job_t::finish_p
bool finish_p
Definition: mjit_worker.c:1122
rb_w32_start_process
MJIT_FUNC_EXPORTED HANDLE rb_w32_start_process(const char *abspath, char *const *argv, int out_fd)
Definition: win32.c:1293
rb_mjit_unit::iseq
rb_iseq_t * iseq
Definition: mjit_worker.c:131
const
#define const
Definition: strftime.c:103
rb_call_cache
Definition: internal.h:2355
realloc
void * realloc(void *, size_t) __attribute__((__warn_unused_result__)) __attribute__((__alloc_size__(2)))
mjit_copy_job_t
Definition: mjit_worker.c:1118
mjit_options::wait
unsigned int wait
Definition: rb_mjit_min_header-2.7.0.h:11730
memcpy
void * memcpy(void *__restrict, const void *__restrict, size_t)
rb_native_cond_initialize
void rb_native_cond_initialize(rb_nativethread_cond_t *cond)
mjit_enabled
bool mjit_enabled
Definition: mjit_worker.c:177
calloc
void * calloc(size_t, size_t) __attribute__((__malloc__)) __attribute__((__warn_unused_result__)) __attribute__((__alloc_size__(1
strerror
RUBY_EXTERN char * strerror(int)
Definition: strerror.c:11
WEXITSTATUS
#define WEXITSTATUS(status)
Definition: error.c:47
fprintf
int fprintf(FILE *__restrict, const char *__restrict,...) __attribute__((__format__(__printf__
STDERR_FILENO
#define STDERR_FILENO
Definition: rb_mjit_min_header-2.7.0.h:3362
PCH_FAILED
@ PCH_FAILED
Definition: mjit_worker.c:232
__pthread_mutex_t
Definition: rb_mjit_min_header-2.7.0.h:1346
MJIT_LDSHARED
#define MJIT_LDSHARED
Definition: mjit_config.h:14
remove
int int remove(const char *)
list_head
Definition: rb_mjit_min_header-2.7.0.h:8975
rb_iseq_location_struct::label
VALUE label
Definition: vm_core.h:275
rb_mjit_unit::o_file_inherited_p
bool o_file_inherited_p
Definition: mjit_worker.c:138
rb_serial_t
unsigned long rb_serial_t
Definition: internal.h:1014
i
uint32_t i
Definition: rb_mjit_min_header-2.7.0.h:5464
list_for_each
#define list_for_each(h, i, member)
Definition: rb_mjit_min_header-2.7.0.h:9094
SIGCHLD_LOSSY
#define SIGCHLD_LOSSY
Definition: vm_core.h:120
rb_mjit_compile_info
Definition: rb_mjit_min_header-2.7.0.h:11735
rb_workqueue_register
int rb_workqueue_register(unsigned flags, rb_postponed_job_func_t, void *)
Definition: vm_trace.c:1637
mjit.h
LIST_HEAD_INIT
#define LIST_HEAD_INIT(name)
Definition: rb_mjit_min_header-2.7.0.h:8982
alloca
#define alloca(size)
Definition: rb_mjit_min_header-2.7.0.h:2487
rb_mjit_unit::handle
void * handle
Definition: mjit_worker.c:130
COMPILER_WARNING_POP
#define COMPILER_WARNING_POP
Definition: internal.h:2665
mjit_options::save_temps
char save_temps
Definition: rb_mjit_min_header-2.7.0.h:11726
list_add_tail
#define list_add_tail(h, n)
Definition: rb_mjit_min_header-2.7.0.h:9023
mjit_worker
void mjit_worker(void)
Definition: mjit_worker.c:1194
va_end
#define va_end(v)
Definition: rb_mjit_min_header-2.7.0.h:3979
WIFSIGNALED
#define WIFSIGNALED(w)
Definition: process.c:105
RUBY_SIGCHLD
#define RUBY_SIGCHLD
Definition: vm_core.h:113
sprintf
int sprintf(char *__restrict, const char *__restrict,...) __attribute__((__format__(__printf__
list_empty
#define list_empty(h)
Definition: rb_mjit_min_header-2.7.0.h:9030
vm_core.h
stderr
#define stderr
Definition: rb_mjit_min_header-2.7.0.h:1485
mjit_valid_class_serial_p
bool mjit_valid_class_serial_p(rb_serial_t class_serial)
Definition: mjit_worker.c:475
strcpy
char * strcpy(char *__restrict, const char *__restrict)
rb_mjit_unit_list
Definition: mjit_worker.c:152
rb_native_cond_destroy
void rb_native_cond_destroy(rb_nativethread_cond_t *cond)
rb_native_cond_broadcast
void rb_native_cond_broadcast(rb_nativethread_cond_t *cond)
size
int size
Definition: encoding.c:58
rb_native_mutex_unlock
void rb_native_mutex_unlock(rb_nativethread_lock_t *lock)
FIXNUM_P
#define FIXNUM_P(f)
Definition: ruby.h:396
CC_CODEFLAG_ARGS
#define CC_CODEFLAG_ARGS
Definition: mjit_worker.c:298
append_lit
#define append_lit(p, str)
Definition: mjit_worker.c:672
list
struct rb_encoding_entry * list
Definition: encoding.c:56
NOT_ADDED_JIT_ISEQ_FUNC
@ NOT_ADDED_JIT_ISEQ_FUNC
Definition: rb_mjit_min_header-2.7.0.h:11719
LAST_JIT_ISEQ_FUNC
@ LAST_JIT_ISEQ_FUNC
Definition: rb_mjit_min_header-2.7.0.h:11722
PCH_NOT_READY
@ PCH_NOT_READY
Definition: mjit_worker.c:232
PRI_PIDT_PREFIX
#define PRI_PIDT_PREFIX
Definition: rb_mjit_min_header-2.7.0.h:103
getpid
pid_t getpid(void)
waitpid
rb_pid_t waitpid(rb_pid_t, int *, int)
Definition: win32.c:4476
O_BINARY
#define O_BINARY
Definition: _sdbm.c:87
COMPILER_WARNING_PUSH
#define COMPILER_WARNING_PUSH
Definition: internal.h:2664
mjit_warning
mjit_warning(const char *format,...)
Definition: mjit_worker.c:322
clockid_t
int clockid_t
Definition: win32.h:132
rb_native_mutex_lock
void rb_native_mutex_lock(rb_nativethread_lock_t *lock)
strcmp
int strcmp(const char *, const char *)
dup2
RUBY_EXTERN int dup2(int, int)
Definition: dup2.c:27
CLOCK_MONOTONIC
#define CLOCK_MONOTONIC
Definition: win32.h:134
__pthread_cond_t
Definition: rb_mjit_min_header-2.7.0.h:1351
mjit_options::verbose
int verbose
Definition: rb_mjit_min_header-2.7.0.h:11732
rb_cloexec_open
int rb_cloexec_open(const char *pathname, int flags, mode_t mode)
Definition: io.c:292
n
const char size_t n
Definition: rb_mjit_min_header-2.7.0.h:5456
arg
VALUE arg
Definition: rb_mjit_min_header-2.7.0.h:5601
pid_t
__pid_t pid_t
Definition: rb_mjit_min_header-2.7.0.h:1325
rb_native_mutex_destroy
void rb_native_mutex_destroy(rb_nativethread_lock_t *lock)
argv
char ** argv
Definition: ruby.c:223
f
#define f
MJIT_LIBS
#define MJIT_LIBS
Definition: mjit_config.h:16
strdup
char * strdup(const char *) __attribute__((__malloc__)) __attribute__((__warn_unused_result__))
timeval
Definition: missing.h:53
str
char str[HTML_ESCAPE_MAX_LEN+1]
Definition: escape.c:18
mjit_opts
struct mjit_options mjit_opts
Definition: mjit_worker.c:174
rb_iseq_constant_body::total_calls
long unsigned total_calls
Definition: rb_mjit_min_header-2.7.0.h:9594
mjit_copy_cache_from_main_thread
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:1141
umask
mode_t umask(mode_t __mask)
mjit_options::max_cache_size
int max_cache_size
Definition: rb_mjit_min_header-2.7.0.h:11733
MEMCPY
#define MEMCPY(p1, p2, type, n)
Definition: ruby.h:1753
mjit_func_t
VALUE(* mjit_func_t)(rb_execution_context_t *, rb_control_frame_t *)
Definition: rb_mjit_min_header-2.7.0.h:11740
clock_gettime
int clock_gettime(clockid_t, struct timespec *)
Definition: win32.c:4612
list_node
Definition: rb_mjit_min_header-2.7.0.h:8971
fclose
int fclose(FILE *)
free
#define free(x)
Definition: dln.c:52
fputc
int fputc(int, FILE *)
rb_vm_struct
Definition: vm_core.h:576
COMPILER_WARNING_IGNORED
#define COMPILER_WARNING_IGNORED(flag)
Definition: internal.h:2667
errno
int errno
len
uint8_t len
Definition: escape.c:17
mjit_options
Definition: rb_mjit_min_header-2.7.0.h:11724
rb_native_cond_wait
void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex)
intptr_t
int intptr_t
Definition: win32.h:90
DLEXT
#define DLEXT
Definition: rb_mjit_min_header-2.7.0.h:435
rb_iseq_struct::body
struct rb_iseq_constant_body * body
Definition: vm_core.h:460
timespec
Definition: missing.h:60
LONG2FIX
#define LONG2FIX(i)
Definition: ruby.h:265
va_start
#define va_start(v, l)
Definition: rb_mjit_min_header-2.7.0.h:3978
rb_pid_t
#define rb_pid_t
Definition: rb_mjit_min_header-2.7.0.h:99
va_list
__gnuc_va_list va_list
Definition: rb_mjit_min_header-2.7.0.h:836
iseq
const rb_iseq_t * iseq
Definition: rb_mjit_min_header-2.7.0.h:13504
rb_mjit_unit_list::head
struct list_head head
Definition: mjit_worker.c:153
thread.h
util.h
STDOUT_FILENO
#define STDOUT_FILENO
Definition: rb_mjit_min_header-2.7.0.h:3361
GCC_NOSTDLIB_FLAGS
#define GCC_NOSTDLIB_FLAGS
Definition: mjit_worker.c:261
rb_strlen_lit
#define rb_strlen_lit(str)
Definition: intern.h:913
gettimeofday
int gettimeofday(struct timeval *, struct timezone *)
Definition: win32.c:4598
RSTRING_LEN
#define RSTRING_LEN(str)
Definition: ruby.h:1005
list_for_each_safe
#define list_for_each_safe(h, i, nxt, member)
Definition: rb_mjit_min_header-2.7.0.h:9097
ruby_assert.h
rb_native_mutex_initialize
void rb_native_mutex_initialize(rb_nativethread_lock_t *lock)
rb_mjit_unit::used_code_p
char used_code_p
Definition: mjit_worker.c:145
rb_native_cond_signal
void rb_native_cond_signal(rb_nativethread_cond_t *cond)
debug.h
rb_mjit_unit_list::length
int length
Definition: mjit_worker.c:154
ruby_null_device
const char ruby_null_device[]
Definition: file.c:6389
__sFILE
Definition: vsnprintf.c:169
WIFEXITED
#define WIFEXITED(status)
Definition: error.c:43
MJIT_ATOMIC_SET
#define MJIT_ATOMIC_SET(var, val)
Definition: mjit_worker.c:121
rb_vm_struct::waitpid_lock
rb_nativethread_lock_t waitpid_lock
Definition: vm_core.h:591
iseq_inline_storage_entry
Definition: vm_core.h:231
verbose
verbose(int level, const char *format,...)
Definition: mjit_worker.c:303
mjit_call_p
bool mjit_call_p
Definition: mjit_worker.c:180
dln.h
RB_DEBUG_COUNTER_mjit_length_unit_queue
@ RB_DEBUG_COUNTER_mjit_length_unit_queue
Definition: rb_mjit_min_header-2.7.0.h:11697