diff --git a/Makefile.in b/Makefile.in
index 047f781030..875e616138 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -527,7 +527,7 @@ update-simplecov:
 update-coverage: update-simplecov update-simplecov-html update-doclie
 
 INSNS	= opt_sc.inc optinsn.inc optunifs.inc insns.inc insns_info.inc \
-	  vmtc.inc vm.inc mjit_compile.inc
+	  vmtc.inc vm.inc mjit_compile.inc mjit_var.inc
 
 $(INSNS): $(srcdir)/insns.def vm_opts.h \
 	  $(srcdir)/defs/opt_operand.def $(srcdir)/defs/opt_insn_unif.def \
diff --git a/common.mk b/common.mk
index afa2461736..21dee17f96 100644
--- a/common.mk
+++ b/common.mk
@@ -925,6 +925,7 @@ $(srcs_vpath)vm.inc: $(srcdir)/tool/ruby_vm/views/vm.inc.erb
 $(srcs_vpath)mjit_compile.inc: $(srcdir)/tool/ruby_vm/views/mjit_compile.inc.erb \
   $(srcdir)/tool/ruby_vm/views/_mjit_compile_insn.erb $(srcdir)/tool/ruby_vm/views/_mjit_compile_send.erb \
   $(srcdir)/tool/ruby_vm/views/_mjit_compile_insn_body.erb $(srcdir)/tool/ruby_vm/views/_mjit_compile_pc_and_sp.erb
+$(srcs_vpath)mjit_var.inc: $(srcdir)/tool/ruby_vm/views/mjit_var.inc.erb
 
 common-srcs: $(srcs_vpath)parse.c $(srcs_vpath)lex.c $(srcs_vpath)enc/trans/newline.c $(srcs_vpath)id.c \
 	     srcs-lib srcs-ext incs
@@ -2046,6 +2047,7 @@ mjit_compile.$(OBJEXT): {$(VPATH)}internal.h
 mjit_compile.$(OBJEXT): {$(VPATH)}mjit.h
 mjit_compile.$(OBJEXT): {$(VPATH)}mjit_compile.c
 mjit_compile.$(OBJEXT): {$(VPATH)}mjit_compile.inc
+mjit_compile.$(OBJEXT): {$(VPATH)}mjit_var.inc
 mjit_compile.$(OBJEXT): {$(VPATH)}vm_core.h
 load.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
 load.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
diff --git a/miniinit.c b/miniinit.c
index 7284e2e62d..0553eba12d 100644
--- a/miniinit.c
+++ b/miniinit.c
@@ -13,7 +13,7 @@
 #include "ruby/encoding.h"
 
 /* loadpath.c */
-const char ruby_exec_prefix[] = "";
+const char ruby_exec_prefix[] = "./";
 const char ruby_initial_load_paths[] = "";
 
 /* localeinit.c */
diff --git a/mjit.c b/mjit.c
index 0cd2839d80..3a5e33b210 100644
--- a/mjit.c
+++ b/mjit.c
@@ -95,6 +95,7 @@
 #ifdef HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
+#include <libgen.h>
 
 extern void rb_native_mutex_lock(rb_nativethread_lock_t *lock);
 extern void rb_native_mutex_unlock(rb_nativethread_lock_t *lock);
@@ -191,6 +192,8 @@ static rb_nativethread_cond_t mjit_gc_wakeup;
 static int in_gc;
 /* True when JIT is working.  */
 static int in_jit;
+/* Object cache directory */
+static VALUE o_cache_dir = Qfalse;
 
 /* Defined in the client thread before starting MJIT threads:  */
 /* Used C compiler path.  */
@@ -705,6 +708,138 @@ compile_c_to_so(const char *c_file, const char *so_file)
     return exit_code == 0;
 }
 
+/* Compile C file to o. It returns 1 if it succeeds. */
+static int
+compile_c_to_o(const char *c_file, const char *o_file)
+{
+    int exit_code;
+    const char *files[] = {
+#ifdef __clang__
+        "-include-pch", NULL,
+#endif
+        "-c",
+#ifndef _MSC_VER
+        "-o",
+#endif
+        NULL, NULL, NULL};
+    const char *libs[] = {
+#ifdef _WIN32
+# ifdef _MSC_VER
+        MJIT_LIBS
+        "-link",
+        libruby_installed,
+        libruby_build,
+# else
+        /* Look for ruby.dll.a in build and install directories. */
+        libruby_installed,
+        libruby_build,
+        MJIT_LIBS
+        "-lmsvcrt",
+        "-lgcc",
+# endif
+#endif
+        NULL};
+    char **args;
+#ifdef _MSC_VER
+    char *p;
+    int olen;
+#endif
+
+    files[numberof(files)-2] = c_file;
+#ifdef _MSC_VER
+    olen = strlen(o_file);
+    files[0] = p = xmalloc(rb_strlen_lit("-Fe") + olen + 1);
+    p = append_lit(p, "-Fe");
+    p = append_str2(p, o_file, olen);
+    *p = '\0';
+#else
+# ifdef __clang__
+    files[1] = pch_file;
+# endif
+    files[numberof(files)-3] = o_file;
+#endif
+    args = form_args(5, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
+                     files, libs, CC_DLDFLAGS_ARGS);
+    if (args == NULL)
+        return FALSE;
+
+    exit_code = exec_process(cc_path, args);
+    xfree(args);
+#ifdef _MSC_VER
+    xfree((char *)files[0]);
+#endif
+
+    if (exit_code != 0)
+        verbose(2, "compile_c_to_o: compile error: %d", exit_code);
+    return exit_code == 0;
+}
+
+/* Compile var C file and o to so. It returns 1 if it succeeds. */
+static int
+compile_var_and_o_to_so(const char *var_file, const char *o_file, const char *so_file)
+{
+    int exit_code;
+    const char *files[] = {
+#ifdef __clang__
+        "-include-pch", NULL,
+#endif
+#ifndef _MSC_VER
+        "-o",
+#endif
+        NULL, NULL, NULL, NULL};
+    const char *libs[] = {
+#ifdef _WIN32
+# ifdef _MSC_VER
+        MJIT_LIBS
+        "-link",
+        libruby_installed,
+        libruby_build,
+# else
+        /* Look for ruby.dll.a in build and install directories. */
+        libruby_installed,
+        libruby_build,
+        MJIT_LIBS
+        "-lmsvcrt",
+        "-lgcc",
+# endif
+#endif
+        NULL};
+    char **args;
+#ifdef _MSC_VER
+    char *p;
+    int solen;
+#endif
+
+    files[numberof(files)-3] = var_file;
+    files[numberof(files)-2] = o_file;
+#ifdef _MSC_VER
+    solen = strlen(so_file);
+    files[0] = p = xmalloc(rb_strlen_lit("-Fe") + solen + 1);
+    p = append_lit(p, "-Fe");
+    p = append_str2(p, so_file, solen);
+    *p = '\0';
+#else
+# ifdef __clang__
+    files[1] = pch_file;
+# endif
+    files[numberof(files)-4] = so_file;
+#endif
+    args = form_args(5, CC_LDSHARED_ARGS, CC_CODEFLAG_ARGS,
+                     files, libs, CC_DLDFLAGS_ARGS);
+    if (args == NULL)
+        return FALSE;
+
+    exit_code = exec_process(cc_path, args);
+    xfree(args);
+#ifdef _MSC_VER
+    xfree((char *)files[0]);
+#endif
+
+    if (exit_code != 0)
+        verbose(2, "compile_c_to_so: compile error: %d", exit_code);
+    return exit_code == 0;
+}
+
 static void *
 load_func_from_so(const char *so_file, const char *funcname, struct rb_mjit_unit *unit)
 {
@@ -749,26 +884,80 @@ print_jit_result(const char *result, const struct rb_mjit_unit *unit, const doub
             RSTRING_PTR(rb_iseq_path(unit->iseq)), FIX2INT(unit->iseq->body->location.first_lineno), c_file);
 }
 
+static int
+mkparentdir(char *path)
+{
+    struct stat sb;
+    char *parent = dirname(strdupa(path));
+    if (!strcmp(parent, path)) {
+        return 1;
+    }
+    stat(parent, &sb);
+    if (S_ISDIR(sb.st_mode)) {
+        return 0;
+    } else {
+        if (mkparentdir(parent)) {
+            return 1;
+        }
+        return mkdir(parent, 0
+          | S_IRUSR | S_IWUSR | S_IXUSR
+          | S_IRGRP | S_IWGRP | S_IXGRP
+          | S_IROTH | S_IXOTH | S_IXOTH
+        );
+    }
+}
+
 /* Compile ISeq in UNIT and return function pointer of JIT-ed code.
    It may return NOT_COMPILABLE_JIT_ISEQ_FUNC if something went wrong. */
 static mjit_func_t
 convert_unit_to_func(struct rb_mjit_unit *unit)
 {
-    char c_file_buff[70], *c_file = c_file_buff, *so_file, funcname[35];
+    char c_file_buff[70], *c_file = c_file_buff, *so_file, funcname[35], *var_file;
     int success;
     int fd;
+    char create_o_cache = 0;
     FILE *f;
     void *func;
     double start_time, end_time;
     int c_file_len = (int)sizeof(c_file_buff);
     static const char c_ext[] = ".c";
     static const char so_ext[] = DLEXT;
+    char *o_file = NULL;
     const int access_mode =
 #ifdef O_BINARY
         O_BINARY|
 #endif
         O_WRONLY|O_EXCL|O_CREAT;
 
+    if (mjit_opts.cache_objs) {
+        struct stat o_st, r_st;
+        char *rb_file = RSTRING_PTR(rb_iseq_realpath(unit->iseq));
+        if (stat(rb_file, &r_st)) {
+            o_file = NULL;
+            verbose(2, "Can't get stat of '%s'", rb_file);
+        } else {
+            o_file = malloc(RSTRING_LEN(o_cache_dir) + strlen(rb_file) + 15);
+            sprintf(o_file, "%s%s.%d.o", RSTRING_PTR(o_cache_dir), rb_file, FIX2INT(unit->iseq->body->location.first_lineno));
+            verbose(2, "Check Cache '%s'", o_file);
+            if (mkparentdir(o_file)) {
+                verbose(2, "Can't create directory of '%s'", o_file);
+                o_file = NULL;
+            } else if (stat(o_file, &o_st)) {
+                if (errno == ENOENT) {
+                    create_o_cache = 1;
+                    verbose(2, "Create '%s'", o_file);
+                } else {
+                    o_file = NULL;
+                    verbose(2, "Can't get stat of '%s'", o_file);
+                }
+            } else if(o_st.st_mtime > r_st.st_mtime) {
+                verbose(2, "Use cache '%s'", o_file);
+            } else {
+                create_o_cache = 1;
+                verbose(2, "Update '%s'", o_file);
+            }
+        }
+    }
     c_file_len = sprint_uniq_filename(c_file_buff, c_file_len, unit->id, MJIT_TMP_PREFIX, c_ext);
     if (c_file_len >= (int)sizeof(c_file_buff)) {
         ++c_file_len;
@@ -781,6 +970,49 @@ convert_unit_to_func(struct rb_mjit_unit *unit)
     memcpy(&so_file[c_file_len - sizeof(c_ext)], so_ext, sizeof(so_ext));
     sprintf(funcname, "_mjit%d", unit->id);
 
+    if (o_file) {
+        static const char var_suffix[] = "_var";
+        var_file = alloca(c_file_len + sizeof(var_suffix));
+        memcpy(var_file, c_file, c_file_len - sizeof(c_ext));
+        memcpy(&var_file[c_file_len - sizeof(c_ext)], var_suffix, sizeof(var_suffix));
+        memcpy(&var_file[c_file_len - sizeof(c_ext) + sizeof(var_suffix) - 1], c_ext, sizeof(c_ext));
+        verbose(2, "Create '%s'", var_file);
+
+        fd = rb_cloexec_open(var_file, access_mode, 0600);
+        if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
+            int e = errno;
+            if (fd >= 0) (void)close(fd);
+            verbose(1, "Failed to fopen '%s', giving up JIT for it (%s)", var_file, strerror(e));
+            return (mjit_func_t)NOT_COMPILABLE_JIT_ISEQ_FUNC;
+        }
+#ifdef __clang__
+    /* -include-pch is used for Clang */
+#else
+    {
+# ifdef __GNUC__
+        const char *s = pch_file;
+# else
+        const char *s = header_file;
+# endif
+        const char *e = header_name_end(s);
+
+        fprintf(f, "#include \"");
+        /* print pch_file except .gch */
+        for (; s < e; s++) {
+            switch(*s) {
+              case '\\': case '"':
+                fputc('\\', f);
+            }
+            fputc(*s, f);
+        }
+        fprintf(f, "\"\n");
+    }
+#endif
+        mjit_compile_var(f, unit->iseq->body, funcname);
+        fclose(f);
+    }
+
+    if (!o_file || create_o_cache) {
     fd = rb_cloexec_open(c_file, access_mode, 0600);
     if (fd < 0 || (f = fdopen(fd, "w")) == NULL) {
         int e = errno;
@@ -835,7 +1067,8 @@ convert_unit_to_func(struct rb_mjit_unit *unit)
         verbose(2, "start compile: %s@%s:%d -> %s", label, path, lineno, c_file);
         fprintf(f, "/* %s@%s:%d */\n\n", label, path, lineno);
     }
-    success = mjit_compile(f, unit->iseq->body, funcname);
+    success = mjit_compile(f, unit->iseq->body, funcname, create_o_cache);
+    fclose(f);
 
     /* release blocking mjit_gc_start_hook */
     CRITICAL_SECTION_START(3, "after mjit_compile to wakeup client for GC");
@@ -844,16 +1077,26 @@ convert_unit_to_func(struct rb_mjit_unit *unit)
     rb_native_cond_signal(&mjit_client_wakeup);
     CRITICAL_SECTION_FINISH(3, "in worker to wakeup client for GC");
 
-    fclose(f);
     if (!success) {
         if (!mjit_opts.save_temps)
             remove(c_file);
         print_jit_result("failure", unit, 0, c_file);
         return (mjit_func_t)NOT_COMPILABLE_JIT_ISEQ_FUNC;
     }
+    }
 
     start_time = real_ms_time();
-    success = compile_c_to_so(c_file, so_file);
+    if (o_file) {
+        if (create_o_cache) {
+            success = compile_c_to_o(c_file, o_file);
+            success = compile_var_and_o_to_so(var_file, o_file, so_file) && success;
+        } else {
+            success = compile_var_and_o_to_so(var_file, o_file, so_file);
+        }
+        free(o_file);
+    } else {
+        success = compile_c_to_so(c_file, so_file);
+    }
     end_time = real_ms_time();
 
     if (!mjit_opts.save_temps)
@@ -1387,6 +1630,16 @@ mjit_init(struct mjit_options *opts)
         rb_native_cond_destroy(&mjit_gc_wakeup);
         verbose(1, "Failure in MJIT thread initialization\n");
     }
+
+    /* Initialize Object cache directory */
+    if (mjit_opts.cache_objs) {
+        o_cache_dir = rb_default_home_dir(rb_str_new(0, 0));
+        rb_str_concat(o_cache_dir, rb_str_new2("/"));
+        rb_str_concat(o_cache_dir, rb_str_new2(".cache"));
+        rb_str_concat(o_cache_dir, rb_str_new2("/"));
+        rb_str_concat(o_cache_dir, rb_str_new2("ruby-mjit"));
+        rb_str_concat(o_cache_dir, rb_str_new2("/"));
+    }
 }
 
 /* Finish the threads processing units and creating PCH, finalize
@@ -1450,6 +1703,7 @@ mjit_mark(void)
     if (!mjit_init_p)
         return;
     RUBY_MARK_ENTER("mjit");
+    RUBY_MARK_UNLESS_NULL(o_cache_dir);
     CRITICAL_SECTION_START(4, "mjit_mark");
     for (node = unit_queue.head; node != NULL; node = node->next) {
         if (node->unit->iseq) { /* ISeq is still not GCed */
diff --git a/mjit.h b/mjit.h
index 9bf2a00913..257843694e 100644
--- a/mjit.h
+++ b/mjit.h
@@ -47,6 +47,7 @@ struct mjit_options {
     /* Maximal permitted number of iseq JIT codes in a MJIT memory
        cache.  */
     int max_cache_size;
+    char cache_objs; /* flag of MJIT cache object files */
 };
 
 typedef VALUE (*mjit_func_t)(rb_execution_context_t *, rb_control_frame_t *);
@@ -59,7 +60,8 @@ extern void mjit_add_iseq_to_process(const rb_iseq_t *iseq);
 extern mjit_func_t mjit_get_iseq_func(const struct rb_iseq_constant_body *body);
 RUBY_SYMBOL_EXPORT_END
 
-extern int mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname);
+extern int mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname, const char create_o_cache);
+extern void mjit_compile_var(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname);
 extern void mjit_init(struct mjit_options *opts);
 extern void mjit_finish(void);
 extern void mjit_gc_start_hook(void);
diff --git a/mjit_compile.c b/mjit_compile.c
index 5bf14a1388..a99410da77 100644
--- a/mjit_compile.c
+++ b/mjit_compile.c
@@ -101,7 +101,7 @@ comment_id(FILE *f, ID id)
 }
 
 static void compile_insns(FILE *f, const struct rb_iseq_constant_body *body, unsigned int stack_size,
-                          unsigned int pos, struct compile_status *status);
+                          unsigned int pos, struct compile_status *status, const char create_o_cache);
 
 /* Main function of JIT compilation, vm_exec_core counterpart for JIT. Compile one insn to `f`, may modify
    b->stack_size and return next position.
@@ -111,7 +111,7 @@ static void compile_insns(FILE *f, const struct rb_iseq_constant_body *body, uns
    does not have it can be compiled as usual. */
 static unsigned int
 compile_insn(FILE *f, const struct rb_iseq_constant_body *body, const int insn, const VALUE *operands,
-             const unsigned int pos, struct compile_status *status, struct compile_branch *b)
+             const unsigned int pos, struct compile_status *status, struct compile_branch *b, const char create_o_cache)
 {
     unsigned int next_pos = pos + insn_len(insn);
 
@@ -126,7 +126,7 @@ compile_insn(FILE *f, const struct rb_iseq_constant_body *body, const int insn,
    called multiple times for each branch.  */
 static void
 compile_insns(FILE *f, const struct rb_iseq_constant_body *body, unsigned int stack_size,
-              unsigned int pos, struct compile_status *status)
+              unsigned int pos, struct compile_status *status, const char create_o_cache)
 {
     int insn;
     struct compile_branch branch;
@@ -143,7 +143,7 @@ compile_insns(FILE *f, const struct rb_iseq_constant_body *body, unsigned int st
         status->compiled_for_pos[pos] = TRUE;
 
         fprintf(f, "\nlabel_%d: /* %s */\n", pos, insn_name(insn));
-        pos = compile_insn(f, body, insn, body->iseq_encoded + (pos+1), pos, status, &branch);
+        pos = compile_insn(f, body, insn, body->iseq_encoded + (pos+1), pos, status, &branch, create_o_cache);
         if (status->success && branch.stack_size > body->stack_max) {
             if (mjit_opts.warnings || mjit_opts.verbose)
                 fprintf(stderr, "MJIT warning: JIT stack exceeded its max\n");
@@ -170,13 +170,33 @@ compile_cancel_handler(FILE *f, const struct rb_iseq_constant_body *body, struct
 
 /* Compile ISeq to C code in F.  It returns 1 if it succeeds to compile. */
 int
-mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname)
+mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname, const char create_o_cache)
 {
     struct compile_status status;
     status.success = TRUE;
     status.compiled_for_pos = ZALLOC_N(int, body->iseq_size);
     status.local_stack_p = !body->catch_except_p;
 
+    if (create_o_cache) {
+        unsigned int pos = 0;
+        int insn;
+        const VALUE *operands;
+
+        fprintf(f, "extern const VALUE *const original_body_iseq;\n");
+        for (pos = 0; pos < body->iseq_size; pos = pos + insn_len(insn)) {
+#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
+            insn = rb_vm_insn_addr2insn((void *)body->iseq_encoded[pos]);
+#else
+            insn = (int)body->iseq_encoded[pos];
+#endif
+          operands = body->iseq_encoded + (pos+1);
+#define MJIT_EXTERN 1
+/*****************/
+ #include "mjit_var.inc"
+/*****************/
+#undef MJIT_EXTERN
+        }
+    }
 #ifdef _WIN32
     fprintf(f, "__declspec(dllexport)\n");
 #endif
@@ -187,8 +207,10 @@ mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *func
     else {
         fprintf(f, "    VALUE *stack = reg_cfp->sp;\n");
     }
-    fprintf(f, "    static const VALUE *const original_body_iseq = (VALUE *)0x%"PRIxVALUE";\n",
-            (VALUE)body->iseq_encoded);
+    if (!create_o_cache) {
+        fprintf(f, "    static const VALUE *const original_body_iseq = (VALUE *)0x%"PRIxVALUE";\n",
+                (VALUE)body->iseq_encoded);
+    }
 
     /* Simulate `opt_pc` in setup_parameters_complex */
     if (body->param.flags.has_opt) {
@@ -208,10 +230,34 @@ mjit_compile(FILE *f, const struct rb_iseq_constant_body *body, const char *func
     fprintf(f, "        return Qundef;\n");
     fprintf(f, "    }\n");
 
-    compile_insns(f, body, 0, 0, &status);
+    compile_insns(f, body, 0, 0, &status, create_o_cache);
     compile_cancel_handler(f, body, &status);
     fprintf(f, "\n} /* end of %s */\n", funcname);
 
     xfree(status.compiled_for_pos);
     return status.success;
 }
+
+void
+mjit_compile_var(FILE *f, const struct rb_iseq_constant_body *body, const char *funcname)
+{
+    unsigned int pos = 0;
+    int insn;
+    const VALUE *operands;
+
+    fprintf(f, "VALUE *const original_body_iseq = (VALUE *)0x%"PRIxVALUE";\n",
+            (VALUE)body->iseq_encoded);
+    for (pos = 0; pos < body->iseq_size; pos = pos + insn_len(insn)) {
+#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
+        insn = rb_vm_insn_addr2insn((void *)body->iseq_encoded[pos]);
+#else
+        insn = (int)body->iseq_encoded[pos];
+#endif
+      operands = body->iseq_encoded + (pos+1);
+#define MJIT_EXTERN 0
+/*****************/
+ #include "mjit_var.inc"
+/*****************/
+#undef MJIT_EXTERN
+    }
+}
diff --git a/ruby.c b/ruby.c
index 79a9dc6767..b56530edf0 100644
--- a/ruby.c
+++ b/ruby.c
@@ -274,6 +274,7 @@ usage(const char *name, int help)
         M("--jit-debug",         "", "Enable MJIT debugging (very slow)"),
         M("--jit-wait",          "", "Wait until JIT compilation is finished everytime (for testing)"),
         M("--jit-save-temps",    "", "Save MJIT temporary files in $TMP or /tmp (for testing)"),
+        M("--jit-cache-objs",    "", "Cache object files"),
         M("--jit-verbose=num",   "", "Print MJIT logs of level num or less to stderr (default: 0)"),
         M("--jit-max-cache=num", "", "Max number of methods to be JIT-ed in a cache (default: 1000)"),
         M("--jit-min-calls=num", "", "Number of calls to trigger JIT (for testing, default: 5)"),
@@ -937,6 +938,9 @@ setup_mjit_options(const char *s, struct mjit_options *mjit_opt)
     else if (strncmp(s, "-min-calls=", 11) == 0) {
         mjit_opt->min_calls = atoi(s + 11);
     }
+    else if (strcmp(s, "-cache-objs") == 0) {
+        mjit_opt->cache_objs = 1;
+    }
     else {
         rb_raise(rb_eRuntimeError,
                  "invalid MJIT option `%s' (--help will show valid MJIT options)", s + 1);
diff --git a/tool/ruby_vm/models/instructions.rb b/tool/ruby_vm/models/instructions.rb
index 5a04190a19..2b4b8751ac 100644
--- a/tool/ruby_vm/models/instructions.rb
+++ b/tool/ruby_vm/models/instructions.rb
@@ -17,6 +17,11 @@
 RubyVM::Instructions = RubyVM::BareInstructions.to_a + \
                        RubyVM::OperandsUnifications.to_a + \
                        RubyVM::InstructionsUnifications.to_a
+RubyVM::MJIT::UnsupportedInstructions = [
+    'getblockparamproxy',  # TODO: support this
+    'defineclass',         # low priority
+    'opt_call_c_function', # low priority
+]
 
 require_relative 'trace_instructions'
 RubyVM::Instructions.freeze
diff --git a/tool/ruby_vm/views/_mjit_compile_insn.erb b/tool/ruby_vm/views/_mjit_compile_insn.erb
index b3c663441d..ce89cd1d64 100644
--- a/tool/ruby_vm/views/_mjit_compile_insn.erb
+++ b/tool/ruby_vm/views/_mjit_compile_insn.erb
@@ -39,7 +39,15 @@
 %
 % # JIT: Initialize operands
 % insn.opes.each_with_index do |ope, i|
-        fprintf(f, "    <%= ope.fetch(:name) %> = (<%= ope.fetch(:type) %>)0x%"PRIxVALUE";", operands[<%= i %>]);
+%   if !%w(... OFFSET lindex_t rb_num_t).include?(ope.fetch(:type))
+        if (create_o_cache) {
+            fprintf(f, "    <%= ope.fetch(:name) %> = mjit_var_%d_<%= i %>;", pos);
+        } else {
+%   else
+        {
+%   end
+            fprintf(f, "    <%= ope.fetch(:name) %> = (<%= ope.fetch(:type) %>)0x%"PRIxVALUE";", operands[<%= i %>]);
+        }
 %   case ope.fetch(:type)
 %   when 'ID'
         comment_id(f, (ID)operands[<%= i %>]);
@@ -90,7 +98,7 @@
 %
 % # compiler: If insn has conditional JUMP, the branch which is not targeted by JUMP should be compiled too.
 % if insn.expr.expr =~ /if\s+\([^{}]+\)\s+\{[^{}]+JUMP\([^)]+\);[^{}]+\}/
-    compile_insns(f, body, b->stack_size, pos + insn_len(insn), status);
+    compile_insns(f, body, b->stack_size, pos + insn_len(insn), status, create_o_cache);
 % end
 %
 % # compiler: If insn returns (leave) or does longjmp (throw), the branch should no longer be compiled. TODO: create attr for it?
diff --git a/tool/ruby_vm/views/_mjit_compile_send.erb b/tool/ruby_vm/views/_mjit_compile_send.erb
index 5b32ac42db..8ecb9ee5b6 100644
--- a/tool/ruby_vm/views/_mjit_compile_send.erb
+++ b/tool/ruby_vm/views/_mjit_compile_send.erb
@@ -32,8 +32,13 @@
 <%= render 'mjit_compile_pc_and_sp', locals: { insn: insn } -%>
 
 % # JIT: Invalidate call cache if it requires vm_search_method. This allows to inline some of following things.
-            fprintf(f, "    if (UNLIKELY(GET_GLOBAL_METHOD_STATE() != %"PRI_SERIALT_PREFIX"u ||\n", cc->method_state);
-            fprintf(f, "        RCLASS_SERIAL(CLASS_OF(stack[%d])) != %"PRI_SERIALT_PREFIX"u)) {\n", b->stack_size - 1 - argc, cc->class_serial);
+            if (create_o_cache) {
+                fprintf(f, "    if (UNLIKELY(GET_GLOBAL_METHOD_STATE() != mjit_var_%d_method_state ||\n", pos);
+                fprintf(f, "        RCLASS_SERIAL(CLASS_OF(stack[%d])) != mjit_var_%d_class_serial)) {\n", b->stack_size - 1 - argc, pos);
+            } else {
+                fprintf(f, "    if (UNLIKELY(GET_GLOBAL_METHOD_STATE() != %"PRI_SERIALT_PREFIX"u ||\n", cc->method_state);
+                fprintf(f, "        RCLASS_SERIAL(CLASS_OF(stack[%d])) != %"PRI_SERIALT_PREFIX"u)) {\n", b->stack_size - 1 - argc, cc->class_serial);
+            }
             fprintf(f, "        reg_cfp->pc = original_body_iseq + %d;\n", pos);
             fprintf(f, "        goto cancel;\n");
             fprintf(f, "    }\n");
@@ -42,7 +47,11 @@
             fprintf(f, "    {\n");
             fprintf(f, "        struct rb_calling_info calling;\n");
 % if insn.name == 'send'
-            fprintf(f, "        vm_caller_setup_arg_block(ec, reg_cfp, &calling, 0x%"PRIxVALUE", 0x%"PRIxVALUE", FALSE);\n", operands[0], operands[2]);
+            if (create_o_cache) {
+                fprintf(f, "        vm_caller_setup_arg_block(ec, reg_cfp, &calling, mjit_var_%d_0, mjit_var_%d_2, FALSE);\n", pos, pos);
+            } else {
+                fprintf(f, "        vm_caller_setup_arg_block(ec, reg_cfp, &calling, 0x%"PRIxVALUE", 0x%"PRIxVALUE", FALSE);\n", operands[0], operands[2]);
+            }
 % else
             fprintf(f, "        calling.block_handler = VM_BLOCK_HANDLER_NONE;\n");
 % end
@@ -54,9 +63,15 @@
             fprintf(f, "            VALUE v;\n");
             fprintf(f, "            VALUE *argv = reg_cfp->sp - calling.argc;\n");
             fprintf(f, "            reg_cfp->sp = argv - 1;\n"); /* recv */
-            fprintf(f, "            vm_push_frame(ec, (const rb_iseq_t *)0x%"PRIxVALUE", VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling.recv, "
-                    "calling.block_handler, 0x%"PRIxVALUE", (const VALUE *)0x%"PRIxVALUE", argv + %d, %d, %d);\n",
-                    (VALUE)iseq, (VALUE)cc->me, (VALUE)iseq->body->iseq_encoded, param_size, iseq->body->local_table_size - param_size, iseq->body->stack_max);
+            if (create_o_cache) {
+                fprintf(f, "            vm_push_frame(ec, (const rb_iseq_t *)mjit_var_%d_iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling.recv, "
+                        "calling.block_handler, mjit_var_%d_ccme, (const VALUE *)mjit_var_%d_iseq_encoded, argv + %d, %d, %d);\n",
+                        pos, pos, pos, param_size, iseq->body->local_table_size - param_size, iseq->body->stack_max);
+            } else {
+                fprintf(f, "            vm_push_frame(ec, (const rb_iseq_t *)0x%"PRIxVALUE", VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL, calling.recv, "
+                        "calling.block_handler, 0x%"PRIxVALUE", (const VALUE *)0x%"PRIxVALUE", argv + %d, %d, %d);\n",
+                        (VALUE)iseq, (VALUE)cc->me, (VALUE)iseq->body->iseq_encoded, param_size, iseq->body->local_table_size - param_size, iseq->body->stack_max);
+            }
             if (iseq->body->catch_except_p) {
                 fprintf(f, "            VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n");
                 fprintf(f, "            v = vm_exec(ec, TRUE);\n");
diff --git a/tool/ruby_vm/views/mjit_compile.inc.erb b/tool/ruby_vm/views/mjit_compile.inc.erb
index 9961b18453..4bd604bef2 100644
--- a/tool/ruby_vm/views/mjit_compile.inc.erb
+++ b/tool/ruby_vm/views/mjit_compile.inc.erb
@@ -15,11 +15,7 @@
     edit: __FILE__,
 } -%>
 %
-% unsupported_insns = [
-%   'getblockparamproxy',  # TODO: support this
-%   'defineclass',         # low priority
-%   'opt_call_c_function', # low priority
-% ]
+% unsupported_insns = RubyVM::MJIT::UnsupportedInstructions
 %
 % opt_send_without_block = RubyVM::Instructions.find { |i| i.name == 'opt_send_without_block' }
 % if opt_send_without_block.nil?
diff --git a/tool/ruby_vm/views/mjit_var.inc.erb b/tool/ruby_vm/views/mjit_var.inc.erb
new file mode 100644
index 0000000000..21ac7fd3e1
--- /dev/null
+++ b/tool/ruby_vm/views/mjit_var.inc.erb
@@ -0,0 +1,63 @@
+/* -*- mode:c; style:ruby; coding: utf-8; indent-tabs-mode: nil -*- */
+
+% # Copyright (c)   All rights reserved.
+% #
+% # This file is a part of  the programming language Ruby.  Permission is hereby
+% # granted, to either  redistribute and/or modify this file,  provided that the
+% # conditions mentioned  in the  file COPYING  are met.   Consult the  file for
+% # details.
+<%= render 'copyright' %>
+%
+% # This is an ERB template that generates Ruby code that generates C code that
+% # generates JIT-ed C code.
+<%= render 'notice', locals: {
+    this_file: 'is the main part of compile_insn() in mjit_compile.c',
+    edit: __FILE__,
+} -%>
+%
+% unsupported_insns = RubyVM::MJIT::UnsupportedInstructions
+
+switch (insn) {
+% (RubyVM::BareInstructions.to_a + RubyVM::OperandsUnifications.to_a).each do |insn|
+%   next if unsupported_insns.include?(insn.name)
+  case BIN(<%= insn.name %>):
+%   if %w[opt_send_without_block send opt_aref].include?(insn.name)
+#if MJIT_EXTERN
+    fprintf(f, "extern const rb_serial_t mjit_var_%d_method_state;\n", pos);
+    fprintf(f, "extern const rb_serial_t mjit_var_%d_class_serial;\n", pos);
+    fprintf(f, "extern const VALUE mjit_var_%d_iseq;\n", pos);
+    fprintf(f, "extern const VALUE mjit_var_%d_ccme;\n", pos);
+    fprintf(f, "extern const VALUE mjit_var_%d_iseq_encoded;\n", pos);
+#else
+    {
+        CALL_INFO ci = (CALL_INFO)operands[0];
+        CALL_CACHE cc = (CALL_CACHE)operands[1];
+        const rb_iseq_t *iseq = get_iseq_if_available(cc);
+        if (inlinable_iseq_p(ci, cc, iseq = get_iseq_if_available(cc))) {
+            fprintf(f, "const rb_serial_t mjit_var_%d_method_state = %"PRI_SERIALT_PREFIX"u;", pos, cc->method_state);
+            fprintf(f, "const rb_serial_t mjit_var_%d_class_serial = %"PRI_SERIALT_PREFIX"u;", pos, cc->class_serial);
+            fprintf(f, "const VALUE mjit_var_%d_iseq = 0x%"PRIxVALUE";\n", pos, (VALUE)iseq);
+            fprintf(f, "const VALUE mjit_var_%d_ccme = 0x%"PRIxVALUE";\n", pos, (VALUE)cc->me);
+            fprintf(f, "const VALUE mjit_var_%d_iseq_encoded = 0x%"PRIxVALUE";\n", pos, (VALUE)iseq->body->iseq_encoded);
+        } else {
+            fprintf(f, "const rb_serial_t mjit_var_%d_method_state = 0u;", pos);
+            fprintf(f, "const rb_serial_t mjit_var_%d_class_serial = 0u;", pos);
+            fprintf(f, "const VALUE mjit_var_%d_iseq = 0x0;\n", pos);
+            fprintf(f, "const VALUE mjit_var_%d_ccme = 0x0;\n", pos);
+            fprintf(f, "const VALUE mjit_var_%d_iseq_encoded = 0x0;\n", pos);
+        }
+    }
+#endif
+%   end
+%   insn.opes.each_with_index do |ope, i|
+%     next if %w(... OFFSET lindex_t rb_num_t).include?(ope.fetch(:type))
+    fprintf(f, "/* <%= insn.name %> <%= ope.fetch(:type) %> */;\n");
+#if MJIT_EXTERN
+    fprintf(f, "extern const <%= ope.fetch(:type) %> mjit_var_%d_<%= i %>;\n", pos);
+#else
+    fprintf(f, "const <%= ope.fetch(:type) %> mjit_var_%d_<%= i %> = 0x%"PRIxVALUE";\n", pos, operands[<%= i %>]);
+#endif
+%   end
+    break;
+% end
+}
