From 5b86c04206e208e55773a054b7ee015549861ed0 Mon Sep 17 00:00:00 2001
From: Eric Wong <e@80x24.org>
Date: Thu, 18 Sep 2014 03:39:21 +0000
Subject: [PATCH] rb_call_info_t: reduce from 96 => 88 bytes on 64-bit

Every word saved on rb_call_info_t seems to result in over 50K in startup
savings ("valgrind ruby -e exit").

before:
  total heap usage: 49,111 allocs, 19,776 frees, 8,444,054 bytes allocated
after:
  total heap usage: 49,057 allocs, 19,721 frees, 8,390,916 bytes allocated

Performance does not seem bad on Xeon E3-1230 v3 (8MB cache),
(minor +/- across the board):
http://80x24.org/bmlog-20140918-035740.9493
---
 compile.c       |  2 +-
 vm_core.h       |  5 ++--
 vm_insnhelper.c | 73 ++++++++++++++++++++++++++++++++++++++++++---------------
 vm_insnhelper.h | 28 +++++++++++++++++++---
 4 files changed, 82 insertions(+), 26 deletions(-)

diff --git a/compile.c b/compile.c
index ac4e5ba..116454b 100644
--- a/compile.c
+++ b/compile.c
@@ -986,6 +986,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag)
 {
     rb_call_info_t *ci = (rb_call_info_t *)compile_data_alloc(iseq, sizeof(rb_call_info_t));
     ci->mid = mid;
+    /* ci->fn = no default */
     ci->flag = flag;
     ci->orig_argc = argc;
     ci->argc = argc;
@@ -1003,7 +1004,6 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag)
     ci->class_serial = 0;
     ci->blockptr = 0;
     ci->recv = Qundef;
-    ci->call = 0; /* TODO: should set default function? */
 
     ci->aux.index = iseq->callinfo_size++;
 
diff --git a/vm_core.h b/vm_core.h
index 3f1ddc8..ce2fb76 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -139,7 +139,8 @@ struct rb_control_frame_struct;
 typedef struct rb_call_info_struct {
     /* fixed at compile time */
     ID mid;
-    unsigned int flag;
+    uint16_t fn; /* temporary for method calling */
+    uint16_t flag; /* only uses 9 bits */
     int orig_argc;
     rb_iseq_t *blockiseq;
 
@@ -162,8 +163,6 @@ typedef struct rb_call_info_struct {
 	int missing_reason; /* used by method_missing */
 	int inc_sp; /* used by cfunc */
     } aux;
-
-    VALUE (*call)(struct rb_thread_struct *th, struct rb_control_frame_struct *cfp, struct rb_call_info_struct *ci);
 } rb_call_info_t;
 
 #if 1
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 05ed3c6..5b64779 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -845,7 +845,7 @@ vm_search_method(rb_call_info_t *ci, VALUE recv)
 
     ci->me = rb_method_entry(klass, ci->mid, &ci->defined_class);
     ci->klass = klass;
-    ci->call = vm_call_general;
+    ci->fn = VM_CIFN_GENERAL;
 #if OPT_INLINE_METHOD_CACHE
     ci->method_state = GET_GLOBAL_METHOD_STATE();
     ci->class_serial = RCLASS_SERIAL(klass);
@@ -1251,8 +1251,8 @@ vm_callee_setup_arg(rb_thread_t *th, rb_call_info_t *ci, const rb_iseq_t *iseq,
 	ci->aux.opt_pc = 0;
 	CI_SET_FASTPATH(ci,
 			(UNLIKELY(ci->flag & VM_CALL_TAILCALL) ?
-			 vm_call_iseq_setup_tailcall :
-			 vm_call_iseq_setup_normal),
+			 VM_CIFN_ISEQ_SETUP_TAILCALL :
+			 VM_CIFN_ISEQ_SETUP_NORMAL),
 			(!is_lambda &&
 			 !(ci->flag & VM_CALL_ARGS_SPLAT) && /* argc may differ for each calls */
 			 !(ci->me->flag & NOEX_PROTECTED)));
@@ -1582,7 +1582,7 @@ vm_call_cfunc(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_info_t *ci)
 
     if (!(ci->me->flag & NOEX_PROTECTED) &&
 	!(ci->flag & VM_CALL_ARGS_SPLAT)) {
-	CI_SET_FASTPATH(ci, vm_call_cfunc_latter, 1);
+	CI_SET_FASTPATH(ci, VM_CIFN_CFUNC_LATTER, 1);
     }
     val = vm_call_cfunc_latter(th, reg_cfp, ci);
 
@@ -1602,8 +1602,8 @@ vm_call_cfunc_push_frame(rb_thread_t *th)
     vm_push_frame(th, 0, VM_FRAME_MAGIC_CFUNC, ci->recv, ci->defined_class,
 		  VM_ENVVAL_BLOCK_PTR(ci->blockptr), 0, th->cfp->sp + ci->aux.inc_sp, 1, me);
 
-    if (ci->call != vm_call_general) {
-	ci->call = vm_call_cfunc_with_frame;
+    if (ci->fn != VM_CIFN_GENERAL) {
+	ci->fn = VM_CIFN_CFUNC_WITH_FRAME;
     }
 }
 #else /* OPT_CALL_CFUNC_WITHOUT_FRAME */
@@ -1792,32 +1792,34 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci)
 	  normal_method_dispatch:
 	    switch (ci->me->def->type) {
 	      case VM_METHOD_TYPE_ISEQ:{
-		CI_SET_FASTPATH(ci, vm_call_iseq_setup, enable_fastpath);
+		CI_SET_FASTPATH(ci, VM_CIFN_ISEQ_SETUP, enable_fastpath);
 		return vm_call_iseq_setup(th, cfp, ci);
 	      }
 	      case VM_METHOD_TYPE_NOTIMPLEMENTED:
 	      case VM_METHOD_TYPE_CFUNC:
-		CI_SET_FASTPATH(ci, vm_call_cfunc, enable_fastpath);
+		CI_SET_FASTPATH(ci, VM_CIFN_CFUNC, enable_fastpath);
 		return vm_call_cfunc(th, cfp, ci);
 	      case VM_METHOD_TYPE_ATTRSET:{
 		rb_check_arity(ci->argc, 1, 1);
 		ci->aux.index = 0;
-		CI_SET_FASTPATH(ci, vm_call_attrset, enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT));
+		CI_SET_FASTPATH(ci, VM_CIFN_CALL_ATTRSET,
+			   enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT));
 		return vm_call_attrset(th, cfp, ci);
 	      }
 	      case VM_METHOD_TYPE_IVAR:{
 		rb_check_arity(ci->argc, 0, 0);
 		ci->aux.index = 0;
-		CI_SET_FASTPATH(ci, vm_call_ivar, enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT));
+		CI_SET_FASTPATH(ci, VM_CIFN_IVAR,
+			  enable_fastpath && !(ci->flag & VM_CALL_ARGS_SPLAT));
 		return vm_call_ivar(th, cfp, ci);
 	      }
 	      case VM_METHOD_TYPE_MISSING:{
 		ci->aux.missing_reason = 0;
-		CI_SET_FASTPATH(ci, vm_call_method_missing, enable_fastpath);
+		CI_SET_FASTPATH(ci, VM_CIFN_METHOD_MISSING, enable_fastpath);
 		return vm_call_method_missing(th, cfp, ci);
 	      }
 	      case VM_METHOD_TYPE_BMETHOD:{
-		CI_SET_FASTPATH(ci, vm_call_bmethod, enable_fastpath);
+		CI_SET_FASTPATH(ci, VM_CIFN_BMETHOD, enable_fastpath);
 		return vm_call_bmethod(th, cfp, ci);
 	      }
 	      case VM_METHOD_TYPE_ZSUPER:{
@@ -1844,10 +1846,10 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci)
 	      case VM_METHOD_TYPE_OPTIMIZED:{
 		switch (ci->me->def->body.optimize_type) {
 		  case OPTIMIZED_METHOD_TYPE_SEND:
-		    CI_SET_FASTPATH(ci, vm_call_opt_send, enable_fastpath);
+		    CI_SET_FASTPATH(ci, VM_CIFN_OPT_SEND, enable_fastpath);
 		    return vm_call_opt_send(th, cfp, ci);
 		  case OPTIMIZED_METHOD_TYPE_CALL:
-		    CI_SET_FASTPATH(ci, vm_call_opt_call, enable_fastpath);
+		    CI_SET_FASTPATH(ci, VM_CIFN_OPT_CALL, enable_fastpath);
 		    return vm_call_opt_call(th, cfp, ci);
 		  default:
 		    rb_bug("vm_call_method: unsupported optimized method type (%d)",
@@ -1870,7 +1872,7 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci)
 		}
 		me = rb_method_entry(refinement, ci->mid, &defined_class);
 		if (me) {
-		    if (ci->call == vm_call_super_method) {
+		    if (ci->fn == VM_CIFN_SUPER_METHOD) {
 			rb_control_frame_t *top_cfp = current_method_entry(th, cfp);
 			if (top_cfp->me &&
 			    rb_method_definition_eq(me->def, top_cfp->me->def)) {
@@ -1909,7 +1911,7 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci)
 		    stat |= NOEX_VCALL;
 		}
 		ci->aux.missing_reason = stat;
-		CI_SET_FASTPATH(ci, vm_call_method_missing, 1);
+		CI_SET_FASTPATH(ci, VM_CIFN_METHOD_MISSING, 1);
 		return vm_call_method_missing(th, cfp, ci);
 	    }
 	    else if (!(ci->flag & VM_CALL_OPT_SEND) && (ci->me->flag & NOEX_MASK) & NOEX_PROTECTED) {
@@ -1946,7 +1948,7 @@ vm_call_method(rb_thread_t *th, rb_control_frame_t *cfp, rb_call_info_t *ci)
 	}
 	else {
 	    ci->aux.missing_reason = stat;
-	    CI_SET_FASTPATH(ci, vm_call_method_missing, 1);
+	    CI_SET_FASTPATH(ci, VM_CIFN_METHOD_MISSING, 1);
 	    return vm_call_method_missing(th, cfp, ci);
 	}
     }
@@ -2077,13 +2079,13 @@ vm_search_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_call_inf
     if (!ci->klass) {
 	/* bound instance method of module */
 	ci->aux.missing_reason = NOEX_SUPER;
-	CI_SET_FASTPATH(ci, vm_call_method_missing, 1);
+	CI_SET_FASTPATH(ci, VM_CIFN_METHOD_MISSING, 1);
 	return;
     }
 
     /* TODO: use inline cache */
     ci->me = rb_method_entry(ci->klass, ci->mid, &ci->defined_class);
-    ci->call = vm_call_super_method;
+    ci->fn = VM_CIFN_SUPER_METHOD;
 
     while (iseq && !iseq->klass) {
 	iseq = iseq->parent_iseq;
@@ -2435,3 +2437,36 @@ vm_once_clear(VALUE data)
     is->once.running_thread = NULL;
     return Qnil;
 }
+
+/* test w/o C99 support so we may ensure it works on non-C99 compilers: */
+#if 0 && (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
+#  define map_(en,fn) [en] = fn
+#else
+#  define map_(en,fn) fn
+#endif
+
+static inline vm_cifn
+cifn_lookup(const rb_call_info_t *ci)
+{
+    static const vm_cifn fn_map[] = {
+	map_(VM_CIFN_GENERAL, vm_call_general),
+	map_(VM_CIFN_SUPER_METHOD, vm_call_super_method),
+	map_(VM_CIFN_CFUNC, vm_call_cfunc),
+#if OPT_CALL_CFUNC_WITHOUT_FRAME
+	map_(VM_CIFN_CFUNC_LATTER, vm_call_cfunc_latter),
+#endif
+	map_(VM_CIFN_CFUNC_WITH_FRAME, vm_call_cfunc_with_frame),
+	map_(VM_CIFN_ISEQ_SETUP, vm_call_iseq_setup),
+	map_(VM_CIFN_ISEQ_SETUP_NORMAL, vm_call_iseq_setup_normal),
+	map_(VM_CIFN_ISEQ_SETUP_TAILCALL, vm_call_iseq_setup_tailcall),
+	map_(VM_CIFN_CALL_ATTRSET, vm_call_attrset),
+	map_(VM_CIFN_IVAR, vm_call_ivar),
+	map_(VM_CIFN_BMETHOD, vm_call_bmethod),
+	map_(VM_CIFN_OPT_SEND, vm_call_opt_send),
+	map_(VM_CIFN_OPT_CALL, vm_call_opt_call),
+	map_(VM_CIFN_METHOD_MISSING, vm_call_method_missing)
+    };
+
+    return fn_map[ci->fn];
+}
+#undef map_
diff --git a/vm_insnhelper.h b/vm_insnhelper.h
index 31f8ffc..0bcc6ee 100644
--- a/vm_insnhelper.h
+++ b/vm_insnhelper.h
@@ -164,8 +164,30 @@ enum vm_regan_acttype {
   } \
 } while (0)
 
+typedef VALUE (*vm_cifn)(rb_thread_t *, rb_control_frame_t *, rb_call_info_t *);
+
+static inline vm_cifn cifn_lookup(const rb_call_info_t *);
+enum vm_cifn_type {
+    VM_CIFN_GENERAL = 0,
+    VM_CIFN_SUPER_METHOD,
+    VM_CIFN_CFUNC,
+#if OPT_CALL_CFUNC_WITHOUT_FRAME
+    VM_CIFN_CFUNC_LATTER,
+#endif
+    VM_CIFN_CFUNC_WITH_FRAME,
+    VM_CIFN_ISEQ_SETUP,
+    VM_CIFN_ISEQ_SETUP_NORMAL,
+    VM_CIFN_ISEQ_SETUP_TAILCALL,
+    VM_CIFN_CALL_ATTRSET,
+    VM_CIFN_IVAR,
+    VM_CIFN_BMETHOD,
+    VM_CIFN_OPT_SEND,
+    VM_CIFN_OPT_CALL,
+    VM_CIFN_METHOD_MISSING
+};
+
 #define CALL_METHOD(ci) do { \
-    VALUE v = (*(ci)->call)(th, GET_CFP(), (ci)); \
+    VALUE v = (cifn_lookup((ci)))(th, GET_CFP(), (ci)); \
     if (v == Qundef) { \
 	RESTORE_REGS(); \
 	NEXT_INSN(); \
@@ -184,8 +206,8 @@ enum vm_regan_acttype {
 #endif
 
 #if OPT_CALL_FASTPATH
-#define CI_SET_FASTPATH(ci, func, enabled) do { \
-    if (LIKELY(enabled)) ((ci)->call = (func)); \
+#define CI_SET_FASTPATH(ci, func_idx, enabled) do { \
+    if (LIKELY(enabled)) ((ci)->fn = (func_idx)); \
 } while (0)
 #else
 #define CI_SET_FASTPATH(ci, func, enabled) /* do nothing */
-- 
EW

