From 7ad45edfdfcd1a884894cc0601e0daa3f9c4a733 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Thu, 18 Feb 2016 13:13:51 +0900
Subject: [PATCH] compile.c: don't do tail call optimization if covered by
 catch table

* compile.c (iseq_setup): do tail call optimization after encode
  (iseq_peephole_optimize): remove tail call optimization
  (iseq_optimize): ditto
  (iseq_optimize_tailcall): do tail call optimization if no catch
  entries covers
  (iseq_compile_each): remove unneeded nop instruction

* test/ruby/test_optimization.rb (test_tailcall_rescue): add test
---
 compile.c                      | 125 ++++++++++++++++++++++++-----------------
 test/ruby/test_optimization.rb |  21 +++++++
 2 files changed, 94 insertions(+), 52 deletions(-)

diff --git a/compile.c b/compile.c
index aee1b4e..c1fce3a 100644
--- a/compile.c
+++ b/compile.c
@@ -418,6 +418,7 @@ static int iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * n, int
 static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
 static int iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
 static int iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
+static int iseq_optimize_tailcall(rb_iseq_t *iseq);
 
 static int iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl);
 static int iseq_set_exception_local_table(rb_iseq_t *iseq);
@@ -1129,6 +1130,11 @@ iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
     debugs("[compile step 4.3 (set_optargs_table)] \n");
     if (!iseq_set_optargs_table(iseq)) return COMPILE_NG;
 
+    /* dirty hack: it have to look through catch table */
+    if (ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization &&
+	    ISEQ_COMPILE_DATA(iseq)->option->peephole_optimization)
+	iseq_optimize_tailcall(iseq);
+
     debugs("[compile step 5 (iseq_translate_threaded_code)] \n");
     if (!rb_iseq_translate_threaded_code(iseq)) return COMPILE_NG;
 
@@ -1996,7 +2002,7 @@ remove_unreachable_chunk(rb_iseq_t *iseq, LINK_ELEMENT *i)
 }
 
 static int
-iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
+iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list)
 {
     INSN *iobj = (INSN *)list;
   again:
@@ -2212,52 +2218,6 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
 	}
     }
 
-    if (do_tailcallopt &&
-	(iobj->insn_id == BIN(send) ||
-	 iobj->insn_id == BIN(opt_aref_with) ||
-	 iobj->insn_id == BIN(opt_aset_with) ||
-	 iobj->insn_id == BIN(invokesuper))) {
-	/*
-	 *  send ...
-	 *  leave
-	 * =>
-	 *  send ..., ... | VM_CALL_TAILCALL, ...
-	 *  leave # unreachable
-	 */
-	INSN *piobj = NULL;
-	if (iobj->link.next) {
-	    LINK_ELEMENT *next = iobj->link.next;
-	    do {
-		if (next->type != ISEQ_ELEMENT_INSN) {
-		    next = next->next;
-		    continue;
-		}
-		switch (INSN_OF(next)) {
-		  case BIN(nop):
-		  /*case BIN(trace):*/
-		    next = next->next;
-		    break;
-		  case BIN(leave):
-		    piobj = iobj;
-		  default:
-		    next = NULL;
-		    break;
-		}
-	    } while (next);
-	}
-
-	if (piobj) {
-	    struct rb_call_info *ci = (struct rb_call_info *)piobj->operands[0];
-	    if (piobj->insn_id == BIN(send) || piobj->insn_id == BIN(invokesuper)) {
-		if (piobj->operands[2] == 0) { /* no blockiseq */
-		    ci->flag |= VM_CALL_TAILCALL;
-		}
-	    }
-	    else {
-		ci->flag |= VM_CALL_TAILCALL;
-	    }
-	}
-    }
     return COMPILE_OK;
 }
 
@@ -2339,7 +2299,6 @@ iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
 {
     LINK_ELEMENT *list;
     const int do_peepholeopt = ISEQ_COMPILE_DATA(iseq)->option->peephole_optimization;
-    const int do_tailcallopt = ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization;
     const int do_si = ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction;
     const int do_ou = ISEQ_COMPILE_DATA(iseq)->option->operands_unification;
     list = FIRST_ELEMENT(anchor);
@@ -2347,7 +2306,7 @@ iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
     while (list) {
 	if (list->type == ISEQ_ELEMENT_INSN) {
 	    if (do_peepholeopt) {
-		iseq_peephole_optimize(iseq, list, do_tailcallopt);
+		iseq_peephole_optimize(iseq, list);
 	    }
 	    if (do_si) {
 		iseq_specialized_instruction(iseq, (INSN *)list);
@@ -3648,6 +3607,71 @@ build_postexe_iseq(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *body)
     return Qnil;
 }
 
+static int
+iseq_optimize_tailcall(rb_iseq_t *iseq)
+{
+    unsigned int i;
+    VALUE *encoded = (VALUE *)iseq->body->iseq_encoded;
+    const struct iseq_catch_table *table = iseq->body->catch_table;
+    char *cmap = xcalloc(1, iseq->body->iseq_size);
+
+    if (table) {
+	for (i = table->size; i-- > 0; ) {
+	    const struct iseq_catch_table_entry *entry = &table->entries[i];
+	    memset(cmap + entry->start, 1, entry->end - entry->start);
+	}
+    }
+
+    /*
+     *  send ...
+     *  leave
+     * =>
+     *  send ..., ... | VM_CALL_TAILCALL, ...
+     *  leave # unreachable
+     */
+    for (i = 0; i < iseq->body->iseq_size; ) {
+	int insn = (int)encoded[i];
+	int len = insn_len(insn);
+	if (i + len >= iseq->body->iseq_size) break;
+
+	if (!cmap[i] && BIN(leave) == (int)encoded[i + len]) {
+	    struct rb_call_info *ci = (struct rb_call_info *)encoded[i + 1];
+	    switch (insn) {
+	      case BIN(invokesuper):
+	      case BIN(send):
+		if (encoded[i + 3]) break; /* with block */
+	      case BIN(opt_aref_with):
+	      case BIN(opt_aset_with):
+	      case BIN(opt_send_without_block):
+	      case BIN(opt_length):
+	      case BIN(opt_size):
+	      case BIN(opt_empty_p):
+	      case BIN(opt_succ):
+	      case BIN(opt_not):
+	      case BIN(opt_plus):
+	      case BIN(opt_minus):
+	      case BIN(opt_mult):
+	      case BIN(opt_div):
+	      case BIN(opt_mod):
+	      case BIN(opt_eq):
+	      case BIN(opt_neq):
+	      case BIN(opt_lt):
+	      case BIN(opt_le):
+	      case BIN(opt_gt):
+	      case BIN(opt_ge):
+	      case BIN(opt_ltlt):
+	      case BIN(opt_aref):
+	      case BIN(opt_aset):
+		ci->flag |= VM_CALL_TAILCALL;
+	    }
+	}
+	i += len;
+    }
+
+    xfree(cmap);
+    return COMPILE_OK;
+}
+
 /**
   compile each node
 
@@ -4318,9 +4342,6 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
 	    ADD_INSNL(ret, line, jump, label_miss);
 	    ADD_LABEL(ret, label_hit);
 	    COMPILE(ret, "resbody body", resq->nd_body);
-	    if (ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization) {
-		ADD_INSN(ret, line, nop);
-	    }
 	    ADD_INSN(ret, line, leave);
 	    ADD_LABEL(ret, label_miss);
 	    resq = resq->nd_head;
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 8804931..3dd9c9b 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -291,6 +291,27 @@ def just_yield
     end;
   end
 
+  def test_tailcall_rescue
+    assert_separately([], <<~'end;')
+      def do_raise
+        raise "must be rescued"
+      end
+      options = {
+        tailcall_optimization: true,
+        trace_instruction: false,
+      }
+      RubyVM::InstructionSequence.compile(<<~EOF, __FILE__, __FILE__, __LINE__, options).eval
+        def test_rescue
+          return do_raise if true # suppress unreachable warning
+          1 + 2
+        rescue
+          :ok
+        end
+      EOF
+      assert_equal(:ok, test_rescue)
+    end;
+  end
+
   class Bug10557
     def [](_)
       block_given?
-- 
2.7.1

