Browse Source

From Lua 5.2: Add goto and ::label:: statements.

Mike Pall 13 years ago
parent
commit
ead325b0c9
5 changed files with 362 additions and 172 deletions
  1. 4 1
      src/lj_errmsg.h
  2. 4 1
      src/lj_lex.c
  3. 3 2
      src/lj_lex.h
  4. 350 167
      src/lj_parse.c
  5. 1 1
      src/lj_record.c

+ 4 - 1
src/lj_errmsg.h

@@ -134,8 +134,11 @@ ERRDEF(XFUNARG,	"function arguments expected")
 ERRDEF(XSYMBOL,	"unexpected symbol")
 ERRDEF(XDOTS,	"cannot use " LUA_QL("...") " outside a vararg function")
 ERRDEF(XSYNTAX,	"syntax error")
-ERRDEF(XBREAK,	"no loop to break")
 ERRDEF(XFOR,	LUA_QL("=") " or " LUA_QL("in") " expected")
+ERRDEF(XBREAK,	"no loop to break")
+ERRDEF(XLUNDEF,	"undefined label " LUA_QS)
+ERRDEF(XLDUP,	"duplicate label " LUA_QS)
+ERRDEF(XGSCOPE,	"<goto %s> jumps into the scope of local " LUA_QS)
 
 /* Bytecode reader errors. */
 ERRDEF(BCFMT,	"cannot load incompatible bytecode")

+ 4 - 1
src/lj_lex.c

@@ -272,9 +272,9 @@ static int llex(LexState *ls, TValue *tv)
 	save_and_next(ls);
       } while (lj_char_isident(ls->current));
       s = lj_parse_keepstr(ls, ls->sb.buf, ls->sb.n);
+      setstrV(ls->L, tv, s);
       if (s->reserved > 0)  /* Reserved word? */
 	return TK_OFS + s->reserved;
-      setstrV(ls->L, tv, s);
       return TK_name;
     }
     switch (ls->current) {
@@ -330,6 +330,9 @@ static int llex(LexState *ls, TValue *tv)
     case '~':
       next(ls);
       if (ls->current != '=') return '~'; else { next(ls); return TK_ne; }
+    case ':':
+      next(ls);
+      if (ls->current != ':') return ':'; else { next(ls); return TK_label; }
     case '"':
     case '\'':
       read_string(ls, ls->current, tv);

+ 3 - 2
src/lj_lex.h

@@ -15,10 +15,11 @@
 /* Lua lexer tokens. */
 #define TKDEF(_, __) \
   _(and) _(break) _(do) _(else) _(elseif) _(end) _(false) \
-  _(for) _(function) _(if) _(in) _(local) _(nil) _(not) _(or) \
+  _(for) _(function) _(goto) _(if) _(in) _(local) _(nil) _(not) _(or) \
   _(repeat) _(return) _(then) _(true) _(until) _(while) \
   __(concat, ..) __(dots, ...) __(eq, ==) __(ge, >=) __(le, <=) __(ne, ~=) \
-  __(number, <number>) __(name, <name>) __(string, <string>) __(eof, <eof>)
+  __(label, ::) __(number, <number>) __(name, <name>) __(string, <string>) \
+  __(eof, <eof>)
 
 enum {
   TK_OFS = 256,

+ 350 - 167
src/lj_parse.c

@@ -95,17 +95,28 @@ static int expr_numiszero(ExpDesc *e)
 /* Per-function linked list of scope blocks. */
 typedef struct FuncScope {
   struct FuncScope *prev;	/* Link to outer scope. */
-  BCPos breaklist;		/* Jump list for loop breaks. */
+  MSize vstart;			/* Start of block-local variables. */
   uint8_t nactvar;		/* Number of active vars outside the scope. */
-  uint8_t upval;		/* Some variable in the scope is an upvalue. */
-  uint8_t isbreakable;		/* Scope is a loop and allows a break. */
+  uint8_t flags;		/* Scope flags. */
 } FuncScope;
 
+#define FSCOPE_LOOP		0x01	/* Scope is a (breakable) loop. */
+#define FSCOPE_BREAK		0x02	/* Break used in scope. */
+#define FSCOPE_GOLA		0x04	/* Goto or label used in scope. */
+#define FSCOPE_UPVAL		0x08	/* Upvalue in scope. */
+#define FSCOPE_NOCLOSE		0x10	/* Do not close upvalues. */
+
+#define NAME_BREAK		((GCstr *)(uintptr_t)1)
+
 /* Index into variable stack. */
 typedef uint16_t VarIndex;
 #define LJ_MAX_VSTACK		65536
 
-#define VSTACK_VAR_RW		0x80000000	/* In endpc: R/W variable. */
+/* Flags stored in upper bits of endpc. */
+#define VSTACK_MASK		0x1fffffff	/* Mask for actual endpc. */
+#define VSTACK_VAR_RW		0x80000000	/* R/W variable. */
+#define VSTACK_GOTO		0x40000000	/* Pending goto. */
+#define VSTACK_LABEL		0x20000000	/* Label. */
 
 /* Upvalue map. */
 typedef struct UVMap {
@@ -680,8 +691,7 @@ static BCPos bcemit_jmp(FuncState *fs)
   BCPos j = fs->pc - 1;
   BCIns *ip = &fs->bcbase[j].ins;
   fs->jpc = NO_JMP;
-  if ((int32_t)j >= (int32_t)fs->lasttarget &&
-      bc_op(*ip) == BC_UCLO)
+  if ((int32_t)j >= (int32_t)fs->lasttarget && bc_op(*ip) == BC_UCLO)
     setbc_j(ip, NO_JMP);
   else
     j = bcemit_AJ(fs, BC_JMP, fs->freereg, NO_JMP);
@@ -1009,7 +1019,7 @@ static void lex_match(LexState *ls, LexToken what, LexToken who, BCLine line)
 static GCstr *lex_str(LexState *ls)
 {
   GCstr *s;
-  if (ls->token != TK_name)
+  if (ls->token != TK_name && (LJ_52 || ls->token != TK_goto))
     err_token(ls, TK_name);
   s = strV(&ls->tokenval);
   lj_lex_next(ls);
@@ -1094,7 +1104,7 @@ static MSize var_lookup_uv(FuncState *fs, MSize vidx, ExpDesc *e)
 }
 
 /* Forward declaration. */
-static void scope_uvmark(FuncState *fs, BCReg level);
+static void fscope_uvmark(FuncState *fs, BCReg level);
 
 /* Recursively lookup variables in enclosing functions. */
 static MSize var_lookup_(FuncState *fs, GCstr *name, ExpDesc *e, int first)
@@ -1104,7 +1114,7 @@ static MSize var_lookup_(FuncState *fs, GCstr *name, ExpDesc *e, int first)
     if ((int32_t)reg >= 0) {  /* Local in this function? */
       expr_init(e, VLOCAL, reg);
       if (!first)
-	scope_uvmark(fs, reg);  /* Scope now has an upvalue. */
+	fscope_uvmark(fs, reg);  /* Scope now has an upvalue. */
       return (MSize)(e->u.s.aux = (uint32_t)fs->varmap[reg]);
     } else {
       MSize vidx = var_lookup_(fs->prev, name, e, 0);  /* Var in outer func? */
@@ -1125,6 +1135,170 @@ static MSize var_lookup_(FuncState *fs, GCstr *name, ExpDesc *e, int first)
 #define var_lookup(ls, e) \
   var_lookup_((ls)->fs, lex_str(ls), (e), 1)
 
+/* -- Goto an label handling ---------------------------------------------- */
+
+/* Add a new goto or label. */
+static MSize gola_new(LexState *ls, GCstr *name, uint32_t type, BCPos pc)
+{
+  FuncState *fs = ls->fs;
+  MSize vtop = ls->vtop;
+  if (LJ_UNLIKELY(vtop >= ls->sizevstack)) {
+    if (ls->sizevstack >= LJ_MAX_VSTACK)
+      lj_lex_error(ls, 0, LJ_ERR_XLIMC, LJ_MAX_VSTACK);
+    lj_mem_growvec(ls->L, ls->vstack, ls->sizevstack, LJ_MAX_VSTACK, VarInfo);
+  }
+  lua_assert(name == NAME_BREAK || lj_tab_getstr(fs->kt, name) != NULL);
+  /* NOBARRIER: name is anchored in fs->kt and ls->vstack is not a GCobj. */
+  setgcref(ls->vstack[vtop].name, obj2gco(name));
+  ls->vstack[vtop].startpc = pc;
+  ls->vstack[vtop].endpc = fs->nactvar | type;
+  ls->vtop = vtop+1;
+  return vtop;
+}
+
+#define gola_nactvar(v)		((uint8_t)((v)->endpc))
+#define gola_isgoto(v)		((v)->endpc & VSTACK_GOTO)
+#define gola_islabel(v)		((v)->endpc & VSTACK_LABEL)
+#define gola_isgotolabel(v)	((v)->endpc & (VSTACK_GOTO|VSTACK_LABEL))
+
+/* Patch goto to jump to label. */
+static void gola_patch(LexState *ls, VarInfo *vg, VarInfo *vl)
+{
+  FuncState *fs = ls->fs;
+  BCPos pc = vg->startpc;
+  setgcrefnull(vg->name);  /* Invalidate pending goto. */
+  setbc_a(&fs->bcbase[pc].ins, gola_nactvar(vl));
+  jmp_patch(fs, pc, vl->startpc);
+}
+
+/* Patch goto to close upvalues. */
+static void gola_close(LexState *ls, VarInfo *vg)
+{
+  FuncState *fs = ls->fs;
+  BCPos pc = vg->startpc;
+  BCIns *ip = &fs->bcbase[pc].ins;
+  lua_assert(gola_isgoto(vg));
+  lua_assert(bc_op(*ip) == BC_JMP || bc_op(*ip) == BC_UCLO);
+  setbc_a(ip, gola_nactvar(vg));
+  if (bc_op(*ip) == BC_JMP) {
+    BCPos next = jmp_next(fs, pc);
+    if (next != NO_JMP) jmp_patch(fs, next, pc);  /* Jump to UCLO. */
+    setbc_op(ip, BC_UCLO);  /* Turn into UCLO. */
+    setbc_j(ip, NO_JMP);
+  }
+}
+
+/* Resolve pending forward gotos for label. */
+static void gola_resolve(LexState *ls, MSize idx)
+{
+  VarInfo *vg = ls->vstack + ls->fs->bl->vstart;
+  VarInfo *vl = ls->vstack + idx;
+  for (; vg < vl; vg++)
+    if (gcrefeq(vg->name, vl->name) && gola_isgoto(vg)) {
+      if (gola_nactvar(vg) < gola_nactvar(vl)) {
+	GCstr *name = strref(var_get(ls, ls->fs, gola_nactvar(vg)).name);
+	lua_assert((uintptr_t)name >= VARNAME__MAX);
+	ls->linenumber = ls->fs->bcbase[vg->startpc].line;
+	lj_lex_error(ls, 0, LJ_ERR_XGSCOPE,
+		     strdata(strref(vg->name)), strdata(name));
+      }
+      gola_patch(ls, vg, vl);
+    }
+}
+
+/* Fixup remaining gotos and labels for scope. */
+static void gola_fixup(LexState *ls, FuncScope *bl)
+{
+  VarInfo *v = ls->vstack + bl->vstart;
+  VarInfo *ve = ls->vstack + ls->vtop;
+  for (; v < ve; v++) {
+    GCstr *name = strref(v->name);
+    if (name != NULL) {  /* Only consider remaining valid gotos/labels. */
+      if (gola_islabel(v)) {
+	VarInfo *vg;
+	setgcrefnull(v->name);  /* Invalidate label that goes out of scope. */
+	for (vg = v+1; vg < ve; vg++)  /* Resolve pending backward gotos. */
+	  if (strref(vg->name) == name && gola_isgoto(vg)) {
+	    if ((bl->flags&FSCOPE_UPVAL) && gola_nactvar(vg) > gola_nactvar(v))
+	      gola_close(ls, vg);
+	    gola_patch(ls, vg, v);
+	  }
+      } else if (gola_isgoto(v)) {
+	if (bl->prev) {  /* Propagate goto or break to outer scope. */
+	  bl->prev->flags |= name == NAME_BREAK ? FSCOPE_BREAK : FSCOPE_GOLA;
+	  v->endpc = bl->nactvar | VSTACK_GOTO;
+	  if ((bl->flags & FSCOPE_UPVAL))
+	    gola_close(ls, v);
+	} else {  /* No outer scope: undefined goto label or no loop. */
+	  ls->linenumber = ls->fs->bcbase[v->startpc].line;
+	  if (name == NAME_BREAK)
+	    lj_lex_error(ls, 0, LJ_ERR_XBREAK);
+	  else
+	    lj_lex_error(ls, 0, LJ_ERR_XLUNDEF, strdata(name));
+	}
+      }
+    }
+  }
+}
+
+/* Find existing label. */
+static VarInfo *gola_findlabel(LexState *ls, GCstr *name)
+{
+  VarInfo *v = ls->vstack + ls->fs->bl->vstart;
+  VarInfo *ve = ls->vstack + ls->vtop;
+  for (; v < ve; v++)
+    if (strref(v->name) == name && gola_islabel(v))
+      return v;
+  return NULL;
+}
+
+/* -- Scope handling ------------------------------------------------------ */
+
+/* Begin a scope. */
+static void fscope_begin(FuncState *fs, FuncScope *bl, int flags)
+{
+  bl->nactvar = (uint8_t)fs->nactvar;
+  bl->flags = flags;
+  bl->vstart = fs->ls->vtop;
+  bl->prev = fs->bl;
+  fs->bl = bl;
+  lua_assert(fs->freereg == fs->nactvar);
+}
+
+/* End a scope. */
+static void fscope_end(FuncState *fs)
+{
+  FuncScope *bl = fs->bl;
+  LexState *ls = fs->ls;
+  fs->bl = bl->prev;
+  var_remove(ls, bl->nactvar);
+  fs->freereg = fs->nactvar;
+  lua_assert(bl->nactvar == fs->nactvar);
+  if ((bl->flags & (FSCOPE_UPVAL|FSCOPE_NOCLOSE)) == FSCOPE_UPVAL)
+    bcemit_AJ(fs, BC_UCLO, bl->nactvar, 0);
+  if ((bl->flags & FSCOPE_BREAK)) {
+    if ((bl->flags & FSCOPE_LOOP)) {
+      MSize idx = gola_new(ls, NAME_BREAK, VSTACK_LABEL, fs->pc);
+      ls->vtop = idx;  /* Drop break label immediately. */
+      gola_resolve(ls, idx);
+      return;
+    }  /* else: need the fixup step to propagate the breaks. */
+  } else if (!(bl->flags & FSCOPE_GOLA)) {
+    return;
+  }
+  gola_fixup(ls, bl);
+}
+
+/* Mark scope as having an upvalue. */
+static void fscope_uvmark(FuncState *fs, BCReg level)
+{
+  FuncScope *bl;
+  for (bl = fs->bl; bl && bl->nactvar > level; bl = bl->prev)
+    ;
+  if (bl)
+    bl->flags |= FSCOPE_UPVAL;
+}
+
 /* -- Function state management ------------------------------------------- */
 
 /* Fixup bytecode for prototype. */
@@ -1287,36 +1461,37 @@ static void fs_buf_uleb128(LexState *ls, uint32_t v)
 /* Prepare variable info for prototype. */
 static size_t fs_prep_var(LexState *ls, FuncState *fs, size_t *ofsvar)
 {
-  VarInfo *vstack = fs->ls->vstack;
+  VarInfo *vs = fs->ls->vstack, *ve;
   MSize i, n;
   BCPos lastpc;
   lj_str_resetbuf(&ls->sb);  /* Copy to temp. string buffer. */
   /* Store upvalue names. */
   for (i = 0, n = fs->nuv; i < n; i++) {
-    GCstr *s = strref(vstack[fs->uvloc[i].vidx].name);
+    GCstr *s = strref(vs[fs->uvloc[i].vidx].name);
     MSize len = s->len+1;
     fs_buf_need(ls, len);
     fs_buf_str(ls, strdata(s), len);
   }
   *ofsvar = ls->sb.n;
-  vstack += fs->vbase;
   lastpc = 0;
   /* Store local variable names and compressed ranges. */
-  for (i = 0, n = ls->vtop - fs->vbase; i < n; i++) {
-    GCstr *s = strref(vstack[i].name);
-    BCPos startpc = vstack[i].startpc;
-    BCPos endpc = vstack[i].endpc & ~VSTACK_VAR_RW;
-    if ((uintptr_t)s < VARNAME__MAX) {
-      fs_buf_need(ls, 1 + 2*5);
-      ls->sb.buf[ls->sb.n++] = (uint8_t)(uintptr_t)s;
-    } else {
-      MSize len = s->len+1;
-      fs_buf_need(ls, len + 2*5);
-      fs_buf_str(ls, strdata(s), len);
+  for (ve = vs + ls->vtop, vs += fs->vbase; vs < ve; vs++) {
+    if (!gola_isgotolabel(vs)) {
+      GCstr *s = strref(vs->name);
+      BCPos startpc;
+      if ((uintptr_t)s < VARNAME__MAX) {
+	fs_buf_need(ls, 1 + 2*5);
+	ls->sb.buf[ls->sb.n++] = (uint8_t)(uintptr_t)s;
+      } else {
+	MSize len = s->len+1;
+	fs_buf_need(ls, len + 2*5);
+	fs_buf_str(ls, strdata(s), len);
+      }
+      startpc = vs->startpc;
+      fs_buf_uleb128(ls, startpc-lastpc);
+      fs_buf_uleb128(ls, (vs->endpc & VSTACK_MASK)-startpc);
+      lastpc = startpc;
     }
-    fs_buf_uleb128(ls, startpc-lastpc);
-    fs_buf_uleb128(ls, endpc-startpc);
-    lastpc = startpc;
   }
   fs_buf_need(ls, 1);
   ls->sb.buf[ls->sb.n++] = '\0';  /* Terminator for varinfo. */
@@ -1359,14 +1534,17 @@ static void fs_fixup_ret(FuncState *fs)
 {
   BCPos lastpc = fs->pc;
   if (lastpc <= fs->lasttarget || !bcopisret(bc_op(fs->bcbase[lastpc-1].ins))) {
-    if (fs->flags & PROTO_CHILD)
+    if ((fs->bl->flags & FSCOPE_UPVAL))
       bcemit_AJ(fs, BC_UCLO, 0, 0);
     bcemit_AD(fs, BC_RET0, 0, 1);  /* Need final return. */
   }
+  fs->bl->flags |= FSCOPE_NOCLOSE;  /* Handled above. */
+  fscope_end(fs);
+  lua_assert(fs->bl == NULL);
   /* May need to fixup returns encoded before first function was created. */
   if (fs->flags & PROTO_FIXUP_RETURN) {
     BCPos pc;
-    for (pc = 0; pc < lastpc; pc++) {
+    for (pc = 1; pc < lastpc; pc++) {
       BCIns ins = fs->bcbase[pc].ins;
       BCPos offset;
       switch (bc_op(ins)) {
@@ -1397,9 +1575,7 @@ static GCproto *fs_finish(LexState *ls, BCLine line)
   GCproto *pt;
 
   /* Apply final fixups. */
-  lua_assert(fs->bl == NULL);
   fs_fixup_ret(fs);
-  var_remove(ls, 0);
 
   /* Calculate total size of prototype including all colocated arrays. */
   sizept = sizeof(GCproto) + fs->pc*sizeof(BCIns) + fs->nkgc*sizeof(GCRef);
@@ -1564,7 +1740,8 @@ static void expr_table(LexState *ls, ExpDesc *e)
       if (!expr_isk(&key)) expr_index(fs, e, &key);
       if (expr_isnumk(&key) && expr_numiszero(&key)) needarr = 1; else nhash++;
       lex_check(ls, '=');
-    } else if (ls->token == TK_name && lj_lex_lookahead(ls) == '=') {
+    } else if ((ls->token == TK_name || (!LJ_52 && ls->token == TK_goto)) &&
+	       lj_lex_lookahead(ls) == '=') {
       expr_str(ls, &key);
       lex_check(ls, '=');
       nhash++;
@@ -1657,7 +1834,7 @@ static BCReg parse_params(LexState *ls, int needself)
     var_new_lit(ls, nparams++, "self");
   if (ls->token != ')') {
     do {
-      if (ls->token == TK_name) {
+      if (ls->token == TK_name || (!LJ_52 && ls->token == TK_goto)) {
 	var_new(ls, nparams++, lex_str(ls));
       } else if (ls->token == TK_dots) {
 	lj_lex_next(ls);
@@ -1682,9 +1859,11 @@ static void parse_chunk(LexState *ls);
 static void parse_body(LexState *ls, ExpDesc *e, int needself, BCLine line)
 {
   FuncState fs, *pfs = ls->fs;
+  FuncScope bl;
   GCproto *pt;
   ptrdiff_t oldbase = pfs->bcbase - ls->bcstack;
   fs_init(ls, &fs);
+  fscope_begin(&fs, &bl, 0);
   fs.linedefined = line;
   fs.numparams = (uint8_t)parse_params(ls, needself);
   fs.bcbase = pfs->bcbase + pfs->pc;
@@ -1778,7 +1957,7 @@ static void expr_primary(LexState *ls, ExpDesc *v)
     expr(ls, v);
     lex_match(ls, ')', '(', line);
     expr_discharge(ls->fs, v);
-  } else if (ls->token == TK_name) {
+  } else if (ls->token == TK_name || (!LJ_52 && ls->token == TK_goto)) {
     var_lookup(ls, v);
   } else {
     err_syntax(ls, LJ_ERR_XSYMBOL);
@@ -1964,125 +2143,6 @@ static BCPos expr_cond(LexState *ls)
   return v.f;
 }
 
-/* -- Scope handling ------------------------------------------------------ */
-
-/* Begin a scope. */
-static void scope_begin(FuncState *fs, FuncScope *bl, int isbreakable)
-{
-  bl->breaklist = NO_JMP;
-  bl->isbreakable = (uint8_t)isbreakable;
-  bl->nactvar = (uint8_t)fs->nactvar;
-  bl->upval = 0;
-  bl->prev = fs->bl;
-  fs->bl = bl;
-  lua_assert(fs->freereg == fs->nactvar);
-}
-
-/* End a scope. */
-static void scope_end(FuncState *fs)
-{
-  FuncScope *bl = fs->bl;
-  fs->bl = bl->prev;
-  var_remove(fs->ls, bl->nactvar);
-  fs->freereg = fs->nactvar;
-  lua_assert(bl->nactvar == fs->nactvar);
-  /* A scope is either breakable or has upvalues. */
-  lua_assert(!bl->isbreakable || !bl->upval);
-  if (bl->upval)
-    bcemit_AJ(fs, BC_UCLO, bl->nactvar, 0);
-  else  /* Avoid in upval case, it clears lasttarget and kills UCLO+JMP join. */
-    jmp_tohere(fs, bl->breaklist);
-}
-
-/* Mark scope as having an upvalue. */
-static void scope_uvmark(FuncState *fs, BCReg level)
-{
-  FuncScope *bl;
-  for (bl = fs->bl; bl && bl->nactvar > level; bl = bl->prev)
-    ;
-  if (bl)
-    bl->upval = 1;
-}
-
-/* Parse 'break' statement. */
-static void parse_break(LexState *ls)
-{
-  FuncState *fs = ls->fs;
-  FuncScope *bl;
-  BCReg savefr;
-  int upval = 0;
-  for (bl = fs->bl; bl && !bl->isbreakable; bl = bl->prev)
-    upval |= bl->upval;  /* Collect upvalues in intervening scopes. */
-  if (!bl)  /* Error if no breakable scope found. */
-    err_syntax(ls, LJ_ERR_XBREAK);
-  savefr = fs->freereg;
-  fs->freereg = bl->nactvar;  /* Shrink slots to help data-flow analysis. */
-  if (upval)
-    bcemit_AJ(fs, BC_UCLO, bl->nactvar, 0);  /* Close upvalues. */
-  jmp_append(fs, &bl->breaklist, bcemit_jmp(fs));
-  fs->freereg = savefr;
-}
-
-/* Check for end of block. */
-static int endofblock(LexToken token)
-{
-  switch (token) {
-  case TK_else: case TK_elseif: case TK_end: case TK_until: case TK_eof:
-    return 1;
-  default:
-    return 0;
-  }
-}
-
-/* Parse 'return' statement. */
-static void parse_return(LexState *ls)
-{
-  BCIns ins;
-  FuncState *fs = ls->fs;
-  lj_lex_next(ls);  /* Skip 'return'. */
-  fs->flags |= PROTO_HAS_RETURN;
-  if (endofblock(ls->token) || ls->token == ';') {  /* Bare return. */
-    ins = BCINS_AD(BC_RET0, 0, 1);
-  } else {  /* Return with one or more values. */
-    ExpDesc e;  /* Receives the _last_ expression in the list. */
-    BCReg nret = expr_list(ls, &e);
-    if (nret == 1) {  /* Return one result. */
-      if (e.k == VCALL) {  /* Check for tail call. */
-	BCIns *ip = bcptr(fs, &e);
-	/* It doesn't pay off to add BC_VARGT just for 'return ...'. */
-	if (bc_op(*ip) == BC_VARG) goto notailcall;
-	fs->pc--;
-	ins = BCINS_AD(bc_op(*ip)-BC_CALL+BC_CALLT, bc_a(*ip), bc_c(*ip));
-      } else {  /* Can return the result from any register. */
-	ins = BCINS_AD(BC_RET1, expr_toanyreg(fs, &e), 2);
-      }
-    } else {
-      if (e.k == VCALL) {  /* Append all results from a call. */
-      notailcall:
-	setbc_b(bcptr(fs, &e), 0);
-	ins = BCINS_AD(BC_RETM, fs->nactvar, e.u.s.aux - fs->nactvar);
-      } else {
-	expr_tonextreg(fs, &e);  /* Force contiguous registers. */
-	ins = BCINS_AD(BC_RET, fs->nactvar, nret+1);
-      }
-    }
-  }
-  if (fs->flags & PROTO_CHILD)
-    bcemit_AJ(fs, BC_UCLO, 0, 0);  /* May need to close upvalues first. */
-  bcemit_INS(fs, ins);
-}
-
-/* Parse a block. */
-static void parse_block(LexState *ls)
-{
-  FuncState *fs = ls->fs;
-  FuncScope bl;
-  scope_begin(fs, &bl, 0);
-  parse_chunk(ls);
-  lua_assert(bl.breaklist == NO_JMP);
-  scope_end(fs);
-}
-
 /* -- Assignments --------------------------------------------------------- */
 
 /* List of LHS variables. */
@@ -2243,7 +2303,119 @@ static void parse_func(LexState *ls, BCLine line)
   fs->bcbase[fs->pc - 1].line = line;  /* Set line for the store. */
 }
 
-/* -- Loop and conditional statements ------------------------------------- */
+/* -- Control transfer statements ----------------------------------------- */
+
+/* Check for end of block. */
+static int endofblock(LexToken token)
+{
+  switch (token) {
+  case TK_else: case TK_elseif: case TK_end: case TK_until: case TK_eof:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* Parse 'return' statement. */
+static void parse_return(LexState *ls)
+{
+  BCIns ins;
+  FuncState *fs = ls->fs;
+  lj_lex_next(ls);  /* Skip 'return'. */
+  fs->flags |= PROTO_HAS_RETURN;
+  if (endofblock(ls->token) || ls->token == ';') {  /* Bare return. */
+    ins = BCINS_AD(BC_RET0, 0, 1);
+  } else {  /* Return with one or more values. */
+    ExpDesc e;  /* Receives the _last_ expression in the list. */
+    BCReg nret = expr_list(ls, &e);
+    if (nret == 1) {  /* Return one result. */
+      if (e.k == VCALL) {  /* Check for tail call. */
+	BCIns *ip = bcptr(fs, &e);
+	/* It doesn't pay off to add BC_VARGT just for 'return ...'. */
+	if (bc_op(*ip) == BC_VARG) goto notailcall;
+	fs->pc--;
+	ins = BCINS_AD(bc_op(*ip)-BC_CALL+BC_CALLT, bc_a(*ip), bc_c(*ip));
+      } else {  /* Can return the result from any register. */
+	ins = BCINS_AD(BC_RET1, expr_toanyreg(fs, &e), 2);
+      }
+    } else {
+      if (e.k == VCALL) {  /* Append all results from a call. */
+      notailcall:
+	setbc_b(bcptr(fs, &e), 0);
+	ins = BCINS_AD(BC_RETM, fs->nactvar, e.u.s.aux - fs->nactvar);
+      } else {
+	expr_tonextreg(fs, &e);  /* Force contiguous registers. */
+	ins = BCINS_AD(BC_RET, fs->nactvar, nret+1);
+      }
+    }
+  }
+  if (fs->flags & PROTO_CHILD)
+    bcemit_AJ(fs, BC_UCLO, 0, 0);  /* May need to close upvalues first. */
+  bcemit_INS(fs, ins);
+}
+
+/* Parse 'break' statement. */
+static void parse_break(LexState *ls)
+{
+  ls->fs->bl->flags |= FSCOPE_BREAK;
+  gola_new(ls, NAME_BREAK, VSTACK_GOTO, bcemit_jmp(ls->fs));
+}
+
+/* Parse 'goto' statement. */
+static void parse_goto(LexState *ls)
+{
+  FuncState *fs = ls->fs;
+  GCstr *name = lex_str(ls);
+  VarInfo *vl = gola_findlabel(ls, name);
+  if (vl)  /* Treat backwards goto within same scope like a loop. */
+    bcemit_AJ(fs, BC_LOOP, gola_nactvar(vl), -1);  /* No BC range check. */
+  fs->bl->flags |= FSCOPE_GOLA;
+  gola_new(ls, name, VSTACK_GOTO, bcemit_jmp(fs));
+}
+
+/* Parse label. */
+static void parse_label(LexState *ls)
+{
+  FuncState *fs = ls->fs;
+  GCstr *name;
+  MSize idx;
+  fs->lasttarget = fs->pc;
+  fs->bl->flags |= FSCOPE_GOLA;
+  lj_lex_next(ls);  /* Skip '::'. */
+  name = lex_str(ls);
+  if (gola_findlabel(ls, name))
+    lj_lex_error(ls, 0, LJ_ERR_XLDUP, strdata(name));
+  idx = gola_new(ls, name, VSTACK_LABEL, fs->pc);
+  lex_check(ls, TK_label);
+  /* Recursively parse trailing statements: labels and ';' (Lua 5.2 only). */
+  for (;;) {
+    if (ls->token == TK_label) {
+      synlevel_begin(ls);
+      parse_label(ls);
+      synlevel_end(ls);
+    } else if (LJ_52 && ls->token == ';') {
+      lj_lex_next(ls);
+    } else {
+      break;
+    }
+  }
+  /* Trailing label is considered to be outside of scope. */
+  if (endofblock(ls->token) && ls->token != TK_until)
+    ls->vstack[idx].endpc = fs->bl->nactvar | VSTACK_LABEL;
+  gola_resolve(ls, idx);
+}
+
+/* -- Blocks, loops and conditional statements ---------------------------- */
+
+/* Parse a block. */
+static void parse_block(LexState *ls)
+{
+  FuncState *fs = ls->fs;
+  FuncScope bl;
+  fscope_begin(fs, &bl, 0);
+  parse_chunk(ls);
+  fscope_end(fs);
+}
 
 /* Parse 'while' statement. */
 static void parse_while(LexState *ls, BCLine line)
@@ -2254,13 +2426,13 @@ static void parse_while(LexState *ls, BCLine line)
   lj_lex_next(ls);  /* Skip 'while'. */
   start = fs->lasttarget = fs->pc;
   condexit = expr_cond(ls);
-  scope_begin(fs, &bl, 1);
+  fscope_begin(fs, &bl, FSCOPE_LOOP);
   lex_check(ls, TK_do);
   loop = bcemit_AD(fs, BC_LOOP, fs->nactvar, 0);
   parse_block(ls);
   jmp_patch(fs, bcemit_jmp(fs), start);
   lex_match(ls, TK_end, TK_while, line);
-  scope_end(fs);
+  fscope_end(fs);
   jmp_tohere(fs, condexit);
   jmp_patchins(fs, loop, fs->pc);
 }
@@ -2272,24 +2444,24 @@ static void parse_repeat(LexState *ls, BCLine line)
   BCPos loop = fs->lasttarget = fs->pc;
   BCPos condexit;
   FuncScope bl1, bl2;
-  scope_begin(fs, &bl1, 1);  /* Breakable loop scope. */
-  scope_begin(fs, &bl2, 0);  /* Inner scope. */
+  fscope_begin(fs, &bl1, FSCOPE_LOOP);  /* Breakable loop scope. */
+  fscope_begin(fs, &bl2, 0);  /* Inner scope. */
   lj_lex_next(ls);  /* Skip 'repeat'. */
   bcemit_AD(fs, BC_LOOP, fs->nactvar, 0);
   parse_chunk(ls);
   lex_match(ls, TK_until, TK_repeat, line);
   condexit = expr_cond(ls);  /* Parse condition (still inside inner scope). */
-  if (!bl2.upval) {  /* No upvalues? Just end inner scope. */
-    scope_end(fs);
+  if (!(bl2.flags & FSCOPE_UPVAL)) {  /* No upvalues? Just end inner scope. */
+    fscope_end(fs);
   } else {  /* Otherwise generate: cond: UCLO+JMP out, !cond: UCLO+JMP loop. */
     parse_break(ls);  /* Break from loop and close upvalues. */
     jmp_tohere(fs, condexit);
-    scope_end(fs);  /* End inner scope and close upvalues. */
+    fscope_end(fs);  /* End inner scope and close upvalues. */
     condexit = bcemit_jmp(fs);
   }
   jmp_patch(fs, condexit, loop);  /* Jump backwards if !cond. */
   jmp_patchins(fs, loop, fs->pc);
-  scope_end(fs);  /* End loop scope. */
+  fscope_end(fs);  /* End loop scope. */
 }
 
 /* Parse numeric 'for'. */
@@ -2318,11 +2490,11 @@ static void parse_for_num(LexState *ls, GCstr *varname, BCLine line)
   var_add(ls, 3);  /* Hidden control variables. */
   lex_check(ls, TK_do);
   loop = bcemit_AJ(fs, BC_FORI, base, NO_JMP);
-  scope_begin(fs, &bl, 0);  /* Scope for visible variables. */
+  fscope_begin(fs, &bl, 0);  /* Scope for visible variables. */
   var_add(ls, 1);
   bcreg_reserve(fs, 1);
   parse_block(ls);
-  scope_end(fs);
+  fscope_end(fs);
   /* Perform loop inversion. Loop control instructions are at the end. */
   loopend = bcemit_AJ(fs, BC_FORL, base, NO_JMP);
   fs->bcbase[loopend].line = line;  /* Fix line for control ins. */
@@ -2389,11 +2561,11 @@ static void parse_for_iter(LexState *ls, GCstr *indexname)
   var_add(ls, 3);  /* Hidden control variables. */
   lex_check(ls, TK_do);
   loop = bcemit_AJ(fs, isnext ? BC_ISNEXT : BC_JMP, base, NO_JMP);
-  scope_begin(fs, &bl, 0);  /* Scope for visible variables. */
+  fscope_begin(fs, &bl, 0);  /* Scope for visible variables. */
   var_add(ls, nvars-3);
   bcreg_reserve(fs, nvars-3);
   parse_block(ls);
-  scope_end(fs);
+  fscope_end(fs);
   /* Perform loop inversion. Loop control instructions are at the end. */
   jmp_patchins(fs, loop, fs->pc);
   bcemit_ABC(fs, isnext ? BC_ITERN : BC_ITERC, base, nvars-3+1, 2+1);
@@ -2409,7 +2581,7 @@ static void parse_for(LexState *ls, BCLine line)
   FuncState *fs = ls->fs;
   GCstr *varname;
   FuncScope bl;
-  scope_begin(fs, &bl, 1);  /* Breakable loop scope. */
+  fscope_begin(fs, &bl, FSCOPE_LOOP);
   lj_lex_next(ls);  /* Skip 'for'. */
   varname = lex_str(ls);  /* Get first variable name. */
   if (ls->token == '=')
@@ -2419,7 +2591,7 @@ static void parse_for(LexState *ls, BCLine line)
   else
     err_syntax(ls, LJ_ERR_XFOR);
   lex_match(ls, TK_end, TK_for, line);
-  scope_end(fs);  /* Resolve break list. */
+  fscope_end(fs);  /* Resolve break list. */
 }
 
 /* Parse condition and 'then' block. */
@@ -2500,6 +2672,15 @@ static int parse_stmt(LexState *ls)
     lj_lex_next(ls);
     break;
 #endif
+  case TK_label:
+    parse_label(ls);
+    break;
+  case TK_goto:
+    if (LJ_52 || lj_lex_lookahead(ls) == TK_name) {
+      lj_lex_next(ls);
+      parse_goto(ls);
+      break;
+    }  /* else: fallthrough */
   default:
     parse_call_assign(ls);
     break;
@@ -2526,6 +2707,7 @@ static void parse_chunk(LexState *ls)
 GCproto *lj_parse(LexState *ls)
 {
   FuncState fs;
+  FuncScope bl;
   GCproto *pt;
   lua_State *L = ls->L;
 #ifdef LUAJIT_DISABLE_DEBUGINFO
@@ -2542,6 +2724,7 @@ GCproto *lj_parse(LexState *ls)
   fs.bcbase = NULL;
   fs.bclim = 0;
   fs.flags |= PROTO_VARARG;  /* Main chunk is always a vararg func. */
+  fscope_begin(&fs, &bl, 0);
   bcemit_AD(&fs, BC_FUNCV, 0, 0);  /* Placeholder. */
   lj_lex_next(ls);  /* Read-ahead first token. */
   parse_chunk(ls);

+ 1 - 1
src/lj_record.c

@@ -550,7 +550,7 @@ static void rec_loop_interp(jit_State *J, const BCIns *pc, LoopEvent ev)
       ** an inner loop even in a root trace. But it's better to be a bit
       ** more conservative here and only do it for very short loops.
       */
-      if (!innerloopleft(J, pc))
+      if (bc_j(*pc) != -1 && !innerloopleft(J, pc))
 	lj_trace_err(J, LJ_TRERR_LINNER);  /* Root trace hit an inner loop. */
       if ((ev != LOOPEV_ENTERLO &&
 	   J->loopref && J->cur.nins - J->loopref > 24) || --J->loopunroll < 0)