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;
131 rb_iseq_t *iseq;
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`.
138 bool o_file_inherited_p;
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.
145 char used_code_p;
146 struct list_node unode;
147 // mjit_compile's optimization switches
148 struct rb_mjit_compile_info compile_info;
149 };
150
151 // Linked list of struct rb_mjit_unit.
152 struct rb_mjit_unit_list {
153 struct list_head head;
154 int length; // the list length
155 };
156
157 extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock);
158 extern void rb_native_mutex_unlock(rb_nativethread_lock_t *lock);
159 extern void rb_native_mutex_initialize(rb_nativethread_lock_t *lock);
160 extern void rb_native_mutex_destroy(rb_nativethread_lock_t *lock);
161
162 extern void rb_native_cond_initialize(rb_nativethread_cond_t *cond);
163 extern void rb_native_cond_destroy(rb_nativethread_cond_t *cond);
164 extern void rb_native_cond_signal(rb_nativethread_cond_t *cond);
165 extern void rb_native_cond_broadcast(rb_nativethread_cond_t *cond);
166 extern void rb_native_cond_wait(rb_nativethread_cond_t *cond, rb_nativethread_lock_t *mutex);
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.
174 struct mjit_options mjit_opts;
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[] = {
265 MJIT_CC_COMMON MJIT_CFLAGS GCC_PIC_FLAGS
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
279 GCC_NOSTDLIB_FLAGS NULL
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 {
324 if (mjit_opts.warnings || mjit_opts.verbose) {
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
412 unit->iseq->body->jit_func = (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC;
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
475 mjit_valid_class_serial_p(rb_serial_t class_serial)
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
548 COMPILER_WARNING_PUSH
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 }
610 COMPILER_WARNING_POP
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;
620 rb_nativethread_cond_t cond;
621
622 if (vm) {
623 rb_native_cond_initialize(&cond);
624 rb_native_mutex_lock(&vm->waitpid_lock);
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),
635 RUBY_SIGCHLD, SIGCHLD_LOSSY);
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) {
651 rb_native_mutex_unlock(&vm->waitpid_lock);
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
758 GCC_NOSTDLIB_FLAGS
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.
904 MJIT_ATOMIC_SET(cur->iseq->body->jit_func, (mjit_func_t)func);
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));
1025 return (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC;
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) {
1050 return (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC;
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);
1079 return (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC;
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);
1104 return (mjit_func_t)NOT_COMPILED_JIT_ISEQ_FUNC;
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;
1120 struct rb_call_cache *cc_entries;
1121 union iseq_inline_storage_entry *is_entries;
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
1141 mjit_copy_cache_from_main_thread(const rb_iseq_t *iseq, struct rb_call_cache *cc_entries, union iseq_inline_storage_entry *is_entries)
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
1194 mjit_worker(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