Back with optimization for 'if cond then goto'

Statements like 'if cond then goto label' generate code so that the
jump in the 'if' goes directly to the given label. This optimization
cannot be done when the jump is backwards leaving the scope of some
variable, as it cannot add the needed 'close' instruction. (The jumps
were already generated by the 'if'.)

This commit also added 'likely'/'unlikely' for tests for errors in
the parser, and it changed the way breaks outside loops are detected.
(Now they are detected like other goto's with undefined labels.)
diff --git a/lparser.c b/lparser.c
index 499e953..9419f88 100644
--- a/lparser.c
+++ b/lparser.c
@@ -113,7 +113,7 @@
 
 
 static void check_match (LexState *ls, int what, int who, int where) {
-  if (!testnext(ls, what)) {
+  if (unlikely(!testnext(ls, what))) {
     if (where == ls->linenumber)
       error_expected(ls, what);
     else {
@@ -350,7 +350,7 @@
   Labellist *gl = &ls->dyd->gt;  /* list of goto's */
   Labeldesc *gt = &gl->arr[g];  /* goto to be resolved */
   lua_assert(eqstr(gt->name, label->name));
-  if (gt->nactvar < label->nactvar)  /* enter some scope? */
+  if (unlikely(gt->nactvar < label->nactvar))  /* enter some scope? */
     jumpscopeerror(ls, gt);
   luaK_patchlist(ls->fs, gt->pc, label->pc);
   for (i = g; i < gl->n - 1; i++)  /* remove goto from pending list */
@@ -393,6 +393,11 @@
 }
 
 
+static int newgotoentry (LexState *ls, TString *name, int line, int pc) {
+  return newlabelentry(ls, &ls->dyd->gt, name, line, pc);
+}
+
+
 /*
 ** Solves forward jumps. Check whether new label 'lb' matches any
 ** pending gotos in current block and solves them. Return true
@@ -471,8 +476,15 @@
 ** generates an error for an undefined 'goto'.
 */
 static l_noret undefgoto (LexState *ls, Labeldesc *gt) {
-  const char *msg = "no visible label '%s' for <goto> at line %d";
-  msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line);
+  const char *msg;
+  if (eqstr(gt->name, luaS_newliteral(ls->L, "break"))) {
+    msg = "break outside loop at line %d";
+    msg = luaO_pushfstring(ls->L, msg, gt->line);
+  }
+  else {
+    msg = "no visible label '%s' for <goto> at line %d";
+    msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line);
+  }
   luaK_semerror(ls, msg);
 }
 
@@ -1212,7 +1224,7 @@
   Labeldesc *lb = findlabel(ls, name);
   if (lb == NULL)  /* no label? */
     /* forward jump; will be resolved when the label is declared */
-    newlabelentry(ls, &ls->dyd->gt, name, line, luaK_jump(fs));
+    newgotoentry(ls, name, line, luaK_jump(fs));
   else {  /* found a label */
     /* backward jump; will be resolved here */
     if (fs->nactvar > lb->nactvar)  /* leaving the scope of some variable? */
@@ -1226,15 +1238,10 @@
 /*
 ** Break statement. Semantically equivalent to "goto break".
 */
-static void breakstat (LexState *ls, int pc) {
-  FuncState *fs = ls->fs;
+static void breakstat (LexState *ls) {
   int line = ls->linenumber;
-  BlockCnt *bl = fs->bl;
   luaX_next(ls);  /* skip break */
-  newlabelentry(ls, &ls->dyd->gt, luaS_newliteral(ls->L, "break"), line, pc);
-  while (bl && !bl->isloop) { bl = bl->previous; }
-  if (!bl)
-    luaX_syntaxerror(ls, "no loop to break");
+  newgotoentry(ls, luaS_newliteral(ls->L, "break"), line, luaK_jump(ls->fs));
 }
 
 
@@ -1243,7 +1250,7 @@
 */
 static void checkrepeated (LexState *ls, TString *name) {
   Labeldesc *lb = findlabel(ls, name);
-  if (lb != NULL) {  /* already defined? */
+  if (unlikely(lb != NULL)) {  /* already defined? */
     const char *msg = "label '%s' already defined on line %d";
     msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line);
     luaK_semerror(ls, msg);  /* error */
@@ -1332,7 +1339,7 @@
   int offset = dest - (pc + 1);
   if (back)
     offset = -offset;
-  if (offset > MAXARG_Bx)
+  if (unlikely(offset > MAXARG_Bx))
     luaX_syntaxerror(fs->ls, "control structure too long");
   SETARG_Bx(*jmp, offset);
 }
@@ -1439,28 +1446,67 @@
 }
 
 
+/*
+** Check whether next instruction is a single jump (a 'break', a 'goto'
+** to a forward label, or a 'goto' to a backward label with no variable
+** to close). If so, set the name of the 'label' it is jumping to
+** ("break" for a 'break') or to where it is jumping to ('target') and
+** return true. If not a single jump, leave input unchanged, to be
+** handled as a regular statement.
+*/
+static int issinglejump (LexState *ls, TString **label, int *target) {
+  if (testnext(ls, TK_BREAK)) {  /* a break? */
+    *label = luaS_newliteral(ls->L, "break");
+    return 1;
+  }
+  else if (ls->t.token != TK_GOTO || luaX_lookahead(ls) != TK_NAME)
+    return 0;  /* not a valid goto */
+  else {
+    TString *lname = ls->lookahead.seminfo.ts;  /* label's id */
+    Labeldesc *lb = findlabel(ls, lname);
+    if (lb) {  /* a backward jump? */
+      if (ls->fs->nactvar > lb->nactvar)  /* needs to close variables? */
+        return 0;  /* not a single jump; cannot optimize */
+      *target = lb->pc;
+    }
+    else  /* jump forward */
+      *label = lname;
+    luaX_next(ls);  /* skip goto */
+    luaX_next(ls);  /* skip name */
+    return 1;
+  }
+}
+
+
 static void test_then_block (LexState *ls, int *escapelist) {
   /* test_then_block -> [IF | ELSEIF] cond THEN block */
   BlockCnt bl;
+  int line;
   FuncState *fs = ls->fs;
+  TString *jlb = NULL;
+  int target = NO_JUMP;
   expdesc v;
   int jf;  /* instruction to skip 'then' code (if condition is false) */
   luaX_next(ls);  /* skip IF or ELSEIF */
   expr(ls, &v);  /* read condition */
   checknext(ls, TK_THEN);
-  if (ls->t.token == TK_BREAK) {
+  line = ls->linenumber;
+  if (issinglejump(ls, &jlb, &target)) {  /* 'if x then goto' ? */
     luaK_goiffalse(ls->fs, &v);  /* will jump to label if condition is true */
     enterblock(fs, &bl, 0);  /* must enter block before 'goto' */
-    breakstat(ls, v.t);  /* handle break */
+    if (jlb != NULL)  /* forward jump? */
+      newgotoentry(ls, jlb, line, v.t);  /* will be resolved later */
+    else  /* backward jump */
+      luaK_patchlist(fs, v.t, target);  /* jump directly to 'target' */
     while (testnext(ls, ';')) {}  /* skip semicolons */
-    if (block_follow(ls, 0)) {  /* 'break' is the entire block? */
+    if (block_follow(ls, 0)) {  /* jump is the entire block? */
       leaveblock(fs);
       return;  /* and that is it */
     }
     else  /* must skip over 'then' part if condition is false */
       jf = luaK_jump(fs);
   }
-  else {  /* regular case (not goto/break) */
+  else {  /* regular case (not a jump) */
     luaK_goiftrue(ls->fs, &v);  /* skip over block if condition is false */
     enterblock(fs, &bl, 0);
     jf = v.f;
@@ -1671,7 +1717,7 @@
       break;
     }
     case TK_BREAK: {  /* stat -> breakstat */
-      breakstat(ls, luaK_jump(ls->fs));
+      breakstat(ls);
       break;
     }
     case TK_GOTO: {  /* stat -> 'goto' NAME */
diff --git a/testes/code.lua b/testes/code.lua
index 9b3f2b6..4d44fa6 100644
--- a/testes/code.lua
+++ b/testes/code.lua
@@ -339,8 +339,26 @@
 
 checkequal(
 function (a) while a < 10 do a = a + 1 end end,
-function (a) while true do if not(a < 10) then break end; a = a + 1; end end
+function (a)
+  ::loop::
+  if not (a < 10) then goto exit end
+  a = a + 1
+  goto loop
+::exit::
+end
 )
 
+checkequal(
+function (a) repeat local x = a + 1; a = x until a > 0 end,
+function (a)
+  ::loop:: do
+    local x = a + 1
+    a = x
+  end
+  if not (a > 0) then goto loop end
+end
+)
+
+
 print 'OK'
 
diff --git a/testes/goto.lua b/testes/goto.lua
index 85038d0..92f048f 100644
--- a/testes/goto.lua
+++ b/testes/goto.lua
@@ -249,6 +249,22 @@
 assert(testG(3) == "3")
 assert(testG(4) == 5)
 assert(testG(5) == 10)
+
+do
+  -- if x back goto out of scope of upvalue
+  local X
+  goto L1
+
+  ::L2:: goto L3
+
+  ::L1:: do
+    local scoped a = function () X = true end
+    assert(X == nil)
+    if a then goto L2 end   -- jumping back out of scope of 'a'
+  end
+
+  ::L3:: assert(X == true)   -- checks that 'a' was correctly closed
+end
 --------------------------------------------------------------------------------