New semantics for the integer 'for' loop

The numerical 'for' loop over integers now uses a precomputed counter
to control its number of iteractions. This change eliminates several
weird cases caused by overflows (wrap-around) in the control variable.
(It also ensures that every integer loop halts.)

Also, the special opcodes for the usual case of step==1 were removed.
(The new code is already somewhat complex for the usual case,
but efficient.)
This commit is contained in:
Roberto Ierusalimschy 2019-03-19 10:53:18 -03:00
parent 1e0c73d5b6
commit 9b37a4695e
10 changed files with 213 additions and 185 deletions

View File

@ -99,8 +99,6 @@ static void *disptab[NUM_OPCODES] = {
&&L_OP_RETURN, &&L_OP_RETURN,
&&L_OP_RETURN0, &&L_OP_RETURN0,
&&L_OP_RETURN1, &&L_OP_RETURN1,
&&L_OP_FORLOOP1,
&&L_OP_FORPREP1,
&&L_OP_FORLOOP, &&L_OP_FORLOOP,
&&L_OP_FORPREP, &&L_OP_FORPREP,
&&L_OP_TFORPREP, &&L_OP_TFORPREP,

View File

@ -93,8 +93,6 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 1, 0, 0, iABC) /* OP_RETURN */ ,opmode(0, 1, 0, 0, iABC) /* OP_RETURN */
,opmode(0, 0, 0, 0, iABC) /* OP_RETURN0 */ ,opmode(0, 0, 0, 0, iABC) /* OP_RETURN0 */
,opmode(0, 0, 0, 0, iABC) /* OP_RETURN1 */ ,opmode(0, 0, 0, 0, iABC) /* OP_RETURN1 */
,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP1 */
,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP1 */
,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP */ ,opmode(0, 0, 0, 1, iABx) /* OP_FORLOOP */
,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP */ ,opmode(0, 0, 0, 1, iABx) /* OP_FORPREP */
,opmode(0, 0, 0, 0, iABx) /* OP_TFORPREP */ ,opmode(0, 0, 0, 0, iABx) /* OP_TFORPREP */

View File

@ -280,10 +280,6 @@ OP_RETURN,/* A B C return R(A), ... ,R(A+B-2) (see note) */
OP_RETURN0,/* return */ OP_RETURN0,/* return */
OP_RETURN1,/* A return R(A) */ OP_RETURN1,/* A return R(A) */
OP_FORLOOP1,/* A Bx R(A)++;
if R(A) <= R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
OP_FORPREP1,/* A Bx R(A)--; pc+=Bx */
OP_FORLOOP,/* A Bx R(A)+=R(A+2); OP_FORLOOP,/* A Bx R(A)+=R(A+2);
if R(A) <?= R(A+1) then { pc-=Bx; R(A+3)=R(A) } */ if R(A) <?= R(A+1) then { pc-=Bx; R(A+3)=R(A) } */
OP_FORPREP,/* A Bx R(A)-=R(A+2); pc+=Bx */ OP_FORPREP,/* A Bx R(A)-=R(A+2); pc+=Bx */

View File

@ -84,8 +84,6 @@ static const char *const opnames[] = {
"RETURN", "RETURN",
"RETURN0", "RETURN0",
"RETURN1", "RETURN1",
"FORLOOP1",
"FORPREP1",
"FORLOOP", "FORLOOP",
"FORPREP", "FORPREP",
"TFORPREP", "TFORPREP",

View File

@ -1371,18 +1371,14 @@ static void repeatstat (LexState *ls, int line) {
/* /*
** Read an expression and generate code to put its results in next ** Read an expression and generate code to put its results in next
** stack slot. Return true if expression is a constant integer and, ** stack slot.
** if 'i' is not-zero, its value is equal to 'i'.
** **
*/ */
static int exp1 (LexState *ls, int i) { static void exp1 (LexState *ls) {
expdesc e; expdesc e;
int res;
expr(ls, &e); expr(ls, &e);
res = luaK_isKint(&e) && (i == 0 || i == e.u.ival);
luaK_exp2nextreg(ls->fs, &e); luaK_exp2nextreg(ls->fs, &e);
lua_assert(e.k == VNONRELOC); lua_assert(e.k == VNONRELOC);
return res;
} }
@ -1403,31 +1399,29 @@ static void fixforjump (FuncState *fs, int pc, int dest, int back) {
/* /*
** Generate code for a 'for' loop. 'kind' can be zero (a common for ** Generate code for a 'for' loop.
** loop), one (a basic for loop, with integer values and increment of
** 1), or two (a generic for loop).
*/ */
static void forbody (LexState *ls, int base, int line, int nvars, int kind) { static void forbody (LexState *ls, int base, int line, int nvars, int isgen) {
/* forbody -> DO block */ /* forbody -> DO block */
static OpCode forprep[3] = {OP_FORPREP, OP_FORPREP1, OP_TFORPREP}; static OpCode forprep[2] = {OP_FORPREP, OP_TFORPREP};
static OpCode forloop[3] = {OP_FORLOOP, OP_FORLOOP1, OP_TFORLOOP}; static OpCode forloop[2] = {OP_FORLOOP, OP_TFORLOOP};
BlockCnt bl; BlockCnt bl;
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int prep, endfor; int prep, endfor;
checknext(ls, TK_DO); checknext(ls, TK_DO);
prep = luaK_codeABx(fs, forprep[kind], base, 0); prep = luaK_codeABx(fs, forprep[isgen], base, 0);
enterblock(fs, &bl, 0); /* scope for declared variables */ enterblock(fs, &bl, 0); /* scope for declared variables */
adjustlocalvars(ls, nvars); adjustlocalvars(ls, nvars);
luaK_reserveregs(fs, nvars); luaK_reserveregs(fs, nvars);
block(ls); block(ls);
leaveblock(fs); /* end of scope for declared variables */ leaveblock(fs); /* end of scope for declared variables */
fixforjump(fs, prep, luaK_getlabel(fs), 0); fixforjump(fs, prep, luaK_getlabel(fs), 0);
if (kind == 2) { /* generic for? */ if (isgen) { /* generic for? */
luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars); luaK_codeABC(fs, OP_TFORCALL, base, 0, nvars);
luaK_fixline(fs, line); luaK_fixline(fs, line);
base += 2; /* base for 'OP_TFORLOOP' (skips function and state) */ base += 2; /* base for 'OP_TFORLOOP' (skips function and state) */
} }
endfor = luaK_codeABx(fs, forloop[kind], base, 0); endfor = luaK_codeABx(fs, forloop[isgen], base, 0);
fixforjump(fs, endfor, prep + 1, 1); fixforjump(fs, endfor, prep + 1, 1);
luaK_fixline(fs, line); luaK_fixline(fs, line);
} }
@ -1437,26 +1431,22 @@ static void fornum (LexState *ls, TString *varname, int line) {
/* fornum -> NAME = exp,exp[,exp] forbody */ /* fornum -> NAME = exp,exp[,exp] forbody */
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int base = fs->freereg; int base = fs->freereg;
int basicfor = 1; /* true if it is a "basic" 'for' (integer + 1) */
new_localvarliteral(ls, "(for index)"); new_localvarliteral(ls, "(for index)");
new_localvarliteral(ls, "(for limit)"); new_localvarliteral(ls, "(for limit)");
new_localvarliteral(ls, "(for step)"); new_localvarliteral(ls, "(for step)");
new_localvar(ls, varname); new_localvar(ls, varname);
checknext(ls, '='); checknext(ls, '=');
if (!exp1(ls, 0)) /* initial value not an integer? */ exp1(ls); /* initial value */
basicfor = 0; /* not a basic 'for' */
checknext(ls, ','); checknext(ls, ',');
exp1(ls, 0); /* limit */ exp1(ls); /* limit */
if (testnext(ls, ',')) { if (testnext(ls, ','))
if (!exp1(ls, 1)) /* optional step not 1? */ exp1(ls); /* optional step */
basicfor = 0; /* not a basic 'for' */
}
else { /* default step = 1 */ else { /* default step = 1 */
luaK_int(fs, fs->freereg, 1); luaK_int(fs, fs->freereg, 1);
luaK_reserveregs(fs, 1); luaK_reserveregs(fs, 1);
} }
adjustlocalvars(ls, 3); /* control variables */ adjustlocalvars(ls, 3); /* control variables */
forbody(ls, base, line, 1, basicfor); forbody(ls, base, line, 1, 0);
} }
@ -1484,7 +1474,7 @@ static void forlist (LexState *ls, TString *indexname) {
adjust_assign(ls, 4, explist(ls, &e), &e); adjust_assign(ls, 4, explist(ls, &e), &e);
adjustlocalvars(ls, 4); /* control variables */ adjustlocalvars(ls, 4); /* control variables */
luaK_checkstack(fs, 3); /* extra space to call generator */ luaK_checkstack(fs, 3); /* extra space to call generator */
forbody(ls, base, line, nvars - 4, 2); forbody(ls, base, line, nvars - 4, 1);
} }
@ -1633,7 +1623,7 @@ static void tocloselocalstat (LexState *ls) {
luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr))); luaO_pushfstring(ls->L, "unknown attribute '%s'", getstr(attr)));
new_localvar(ls, str_checkname(ls)); new_localvar(ls, str_checkname(ls));
checknext(ls, '='); checknext(ls, '=');
exp1(ls, 0); exp1(ls);
markupval(fs, fs->nactvar); markupval(fs, fs->nactvar);
fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
adjustlocalvars(ls, 1); adjustlocalvars(ls, 1);

143
lvm.c
View File

@ -148,35 +148,34 @@ int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) {
/* /*
** Try to convert a 'for' limit to an integer, preserving the semantics ** Try to convert a 'for' limit to an integer, preserving the semantics
** of the loop. (The following explanation assumes a non-negative step; ** of the loop. (The following explanation assumes a positive step;
** it is valid for negative steps mutatis mutandis.) ** it is valid for negative steps mutatis mutandis.)
** If the limit is an integer or can be converted to an integer, ** If the limit is an integer or can be converted to an integer,
** rounding down, that is it. ** rounding down, that is it.
** Otherwise, check whether the limit can be converted to a float. If ** Otherwise, check whether the limit can be converted to a float. If
** the number is too large, it is OK to set the limit as LUA_MAXINTEGER, ** the float is too large, clip it to LUA_MAXINTEGER. If the float
** which means no limit. If the number is too negative, the loop ** is too negative, the loop should not run, because any initial
** should not run, because any initial integer value is larger than the ** integer value is greater than such limit; so, it sets 'stopnow'.
** limit. So, it sets the limit to LUA_MININTEGER. 'stopnow' corrects ** (For this latter case, no integer limit would be correct; even a
** the extreme case when the initial value is LUA_MININTEGER, in which ** limit of LUA_MININTEGER would run the loop once for an initial
** case the LUA_MININTEGER limit would still run the loop once. ** value equal to LUA_MININTEGER.)
*/ */
static int forlimit (const TValue *obj, lua_Integer *p, lua_Integer step, static int forlimit (const TValue *lim, lua_Integer *p, lua_Integer step,
int *stopnow) { int *stopnow) {
*stopnow = 0; /* usually, let loops run */ *stopnow = 0; /* usually, let loops run */
if (ttisinteger(obj)) if (!luaV_tointeger(lim, p, (step < 0 ? 2 : 1))) {
*p = ivalue(obj);
else if (!luaV_tointeger(obj, p, (step < 0 ? 2 : 1))) {
/* not coercible to in integer */ /* not coercible to in integer */
lua_Number n; /* try to convert to float */ lua_Number flim; /* try to convert to float */
if (!tonumber(obj, &n)) /* cannot convert to float? */ if (!tonumber(lim, &flim)) /* cannot convert to float? */
return 0; /* not a number */ return 0; /* not a number */
if (luai_numlt(0, n)) { /* if true, float is larger than max integer */ /* 'flim' is a float out of integer bounds */
*p = LUA_MAXINTEGER; if (luai_numlt(0, flim)) { /* if it is positive, it is too large */
if (step < 0) *stopnow = 1; *p = LUA_MAXINTEGER; /* truncate */
if (step < 0) *stopnow = 1; /* initial value must be less than it */
} }
else { /* float is less than min integer */ else { /* it is less than min integer */
*p = LUA_MININTEGER; *p = LUA_MININTEGER; /* truncate */
if (step >= 0) *stopnow = 1; if (step > 0) *stopnow = 1; /* initial value must be greater than it */
} }
} }
return 1; return 1;
@ -1636,85 +1635,87 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
} }
return; return;
} }
vmcase(OP_FORLOOP1) {
lua_Integer idx = intop(+, ivalue(s2v(ra)), 1); /* increment index */
lua_Integer limit = ivalue(s2v(ra + 1));
if (idx <= limit) {
pc -= GETARG_Bx(i); /* jump back */
chgivalue(s2v(ra), idx); /* update internal index... */
setivalue(s2v(ra + 3), idx); /* ...and external index */
}
updatetrap(ci);
vmbreak;
}
vmcase(OP_FORPREP1) {
TValue *init = s2v(ra);
TValue *plimit = s2v(ra + 1);
lua_Integer ilimit, initv;
int stopnow;
if (unlikely(!forlimit(plimit, &ilimit, 1, &stopnow))) {
savestate(L, ci); /* for the error message */
luaG_forerror(L, plimit, "limit");
}
initv = (stopnow ? 0 : ivalue(init));
setivalue(plimit, ilimit);
setivalue(init, intop(-, initv, 1));
pc += GETARG_Bx(i);
vmbreak;
}
vmcase(OP_FORLOOP) { vmcase(OP_FORLOOP) {
if (ttisinteger(s2v(ra))) { /* integer loop? */ if (ttisinteger(s2v(ra + 2))) { /* integer loop? */
lua_Unsigned count = l_castS2U(ivalue(s2v(ra)));
if (count > 0) { /* still more iterations? */
lua_Integer step = ivalue(s2v(ra + 2)); lua_Integer step = ivalue(s2v(ra + 2));
lua_Integer idx = intop(+, ivalue(s2v(ra)), step); /* new index */ lua_Integer idx = ivalue(s2v(ra + 3));
lua_Integer limit = ivalue(s2v(ra + 1)); idx = intop(+, idx, step); /* add step to index */
if ((0 < step) ? (idx <= limit) : (limit <= idx)) { chgivalue(s2v(ra), count - 1); /* update counter... */
setivalue(s2v(ra + 3), idx); /* ...and index */
pc -= GETARG_Bx(i); /* jump back */ pc -= GETARG_Bx(i); /* jump back */
chgivalue(s2v(ra), idx); /* update internal index... */
setivalue(s2v(ra + 3), idx); /* ...and external index */
} }
} }
else { /* floating loop */ else { /* floating loop */
lua_Number step = fltvalue(s2v(ra + 2)); lua_Number step = fltvalue(s2v(ra + 2));
lua_Number limit = fltvalue(s2v(ra + 1)); lua_Number limit = fltvalue(s2v(ra + 1));
lua_Number idx = fltvalue(s2v(ra)); lua_Number idx = fltvalue(s2v(ra + 3));
idx = luai_numadd(L, idx, step); /* inc. index */ idx = luai_numadd(L, idx, step); /* inc. index */
if (luai_numlt(0, step) ? luai_numle(idx, limit) if (luai_numlt(0, step) ? luai_numle(idx, limit)
: luai_numle(limit, idx)) { : luai_numle(limit, idx)) {
setfltvalue(s2v(ra + 3), idx); /* update index */
pc -= GETARG_Bx(i); /* jump back */ pc -= GETARG_Bx(i); /* jump back */
chgfltvalue(s2v(ra), idx); /* update internal index... */
setfltvalue(s2v(ra + 3), idx); /* ...and external index */
} }
} }
updatetrap(ci); updatetrap(ci); /* allows a signal to break the loop */
vmbreak; vmbreak;
} }
vmcase(OP_FORPREP) { vmcase(OP_FORPREP) {
TValue *init = s2v(ra); TValue *pinit = s2v(ra);
TValue *plimit = s2v(ra + 1); TValue *plimit = s2v(ra + 1);
TValue *pstep = s2v(ra + 2); TValue *pstep = s2v(ra + 2);
lua_Integer ilimit; lua_Integer ilimit;
int stopnow; int stopnow;
if (ttisinteger(init) && ttisinteger(pstep) && savestate(L, ci); /* in case of errors */
if (ttisinteger(pinit) && ttisinteger(pstep) &&
forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) { forlimit(plimit, &ilimit, ivalue(pstep), &stopnow)) {
/* all values are integer */ /* integer loop */
lua_Integer initv = (stopnow ? 0 : ivalue(init)); lua_Integer init = ivalue(pinit);
setivalue(plimit, ilimit); lua_Integer step = ivalue(pstep);
setivalue(init, intop(-, initv, ivalue(pstep))); setivalue(s2v(ra + 3), init); /* control variable */
if (step == 0)
luaG_runerror(L, "'for' step is zero");
else if (stopnow)
pc += GETARG_Bx(i) + 1; /* skip the loop */
else if (step > 0) { /* ascending loop? */
if (init > ilimit)
pc += GETARG_Bx(i) + 1; /* skip the loop */
else {
lua_Unsigned count = l_castS2U(ilimit) - l_castS2U(init);
if (step != 1) /* avoid division in the too common case */
count /= l_castS2U(step);
setivalue(s2v(ra), count);
}
}
else { /* descending loop */
if (init < ilimit)
pc += GETARG_Bx(i) + 1; /* skip the loop */
else {
lua_Unsigned count = l_castS2U(init) - l_castS2U(ilimit);
count /= -l_castS2U(step);
setivalue(s2v(ra), count);
}
}
} }
else { /* try making all values floats */ else { /* try making all values floats */
lua_Number ninit; lua_Number nlimit; lua_Number nstep; lua_Number init; lua_Number flimit; lua_Number step;
savestate(L, ci); /* in case of errors */ if (unlikely(!tonumber(plimit, &flimit)))
if (unlikely(!tonumber(plimit, &nlimit)))
luaG_forerror(L, plimit, "limit"); luaG_forerror(L, plimit, "limit");
setfltvalue(plimit, nlimit); setfltvalue(plimit, flimit);
if (unlikely(!tonumber(pstep, &nstep))) if (unlikely(!tonumber(pstep, &step)))
luaG_forerror(L, pstep, "step"); luaG_forerror(L, pstep, "step");
setfltvalue(pstep, nstep); setfltvalue(pstep, step);
if (unlikely(!tonumber(init, &ninit))) if (unlikely(!tonumber(pinit, &init)))
luaG_forerror(L, init, "initial value"); luaG_forerror(L, pinit, "initial value");
setfltvalue(init, luai_numsub(L, ninit, nstep)); if (step == 0)
luaG_runerror(L, "'for' step is zero");
if (luai_numlt(0, step) ? luai_numlt(flimit, init)
: luai_numlt(init, flimit))
pc += GETARG_Bx(i) + 1; /* skip the loop */
else
setfltvalue(s2v(ra + 3), init); /* control variable */
} }
pc += GETARG_Bx(i);
vmbreak; vmbreak;
} }
vmcase(OP_TFORPREP) { vmcase(OP_TFORPREP) {

View File

@ -594,7 +594,7 @@ controls how long the collector waits before starting a new cycle.
The collector starts a new cycle when the use of memory The collector starts a new cycle when the use of memory
hits @M{n%} of the use after the previous collection. hits @M{n%} of the use after the previous collection.
Larger values make the collector less aggressive. Larger values make the collector less aggressive.
Values smaller than 100 mean the collector will not wait to Values less than 100 mean the collector will not wait to
start a new cycle. start a new cycle.
A value of 200 means that the collector waits for the total memory in use A value of 200 means that the collector waits for the total memory in use
to double before starting a new cycle. to double before starting a new cycle.
@ -608,7 +608,7 @@ how many elements it marks or sweeps for each
kilobyte of memory allocated. kilobyte of memory allocated.
Larger values make the collector more aggressive but also increase Larger values make the collector more aggressive but also increase
the size of each incremental step. the size of each incremental step.
You should not use values smaller than 100, You should not use values less than 100,
because they make the collector too slow and because they make the collector too slow and
can result in the collector never finishing a cycle. can result in the collector never finishing a cycle.
The default value is 100; the maximum value is 1000. The default value is 100; the maximum value is 1000.
@ -1004,7 +1004,7 @@ the escape sequence @T{\u{@rep{XXX}}}
(note the mandatory enclosing brackets), (note the mandatory enclosing brackets),
where @rep{XXX} is a sequence of one or more hexadecimal digits where @rep{XXX} is a sequence of one or more hexadecimal digits
representing the character code point. representing the character code point.
This code point can be any value smaller than @M{2@sp{31}}. This code point can be any value less than @M{2@sp{31}}.
(Lua uses the original UTF-8 specification here.) (Lua uses the original UTF-8 specification here.)
Literal strings can also be defined using a long format Literal strings can also be defined using a long format
@ -1370,74 +1370,50 @@ because now @Rw{return} is the last statement in its (inner) block.
The @Rw{for} statement has two forms: The @Rw{for} statement has two forms:
one numerical and one generic. one numerical and one generic.
@sect4{@title{The numerical @Rw{for} loop}
The numerical @Rw{for} loop repeats a block of code while a The numerical @Rw{for} loop repeats a block of code while a
control variable runs through an arithmetic progression. control variable goes through an arithmetic progression.
It has the following syntax: It has the following syntax:
@Produc{ @Produc{
@producname{stat}@producbody{@Rw{for} @bnfNter{Name} @bnfter{=} @producname{stat}@producbody{@Rw{for} @bnfNter{Name} @bnfter{=}
exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}} exp @bnfter{,} exp @bnfopt{@bnfter{,} exp} @Rw{do} block @Rw{end}}
} }
The @emph{block} is repeated for @emph{name} starting at the value of The given identifier (@bnfNter{Name}) defines the control variable,
the first @emph{exp}, until it passes the second @emph{exp} by steps of the which is local to the loop body (@emph{block}).
third @emph{exp}.
More precisely, a @Rw{for} statement like
@verbatim{
for v = @rep{e1}, @rep{e2}, @rep{e3} do @rep{block} end
}
is equivalent to the code:
@verbatim{
do
local @rep{var}, @rep{limit}, @rep{step} = tonumber(@rep{e1}), tonumber(@rep{e2}), tonumber(@rep{e3})
if not (@rep{var} and @rep{limit} and @rep{step}) then error() end
@rep{var} = @rep{var} - @rep{step}
while true do
@rep{var} = @rep{var} + @rep{step}
if (@rep{step} >= 0 and @rep{var} > @rep{limit}) or (@rep{step} < 0 and @rep{var} < @rep{limit}) then
break
end
local v = @rep{var}
@rep{block}
end
end
}
Note the following: The loop starts by evaluating once the three control expressions;
@itemize{ they must all result in numbers.
Their values are called respectively
the @emph{initial value}, the @emph{limit}, and the @emph{step}.
If the step is absent, it defaults @N{to 1}.
Then the loop body is repeated with the value of the control variable
going through an arithmetic progression,
starting at the initial value,
with a common difference given by the step,
until that value passes the limit.
A negative step makes a decreasing sequence;
a step equal to zero raises an error.
If the initial value is already greater than the limit
(or less than, if the step is negative), the body is not executed.
@item{ If both the initial value and the step are integers,
All three control expressions are evaluated only once, the loop is done with integers;
before the loop starts. in this case, the range of the control variable is limited
They must all result in numbers. by the range of integers.
} Otherwise, the loop is done with floats.
(Beware of floating-point accuracy in this case.)
@item{ You should not change the value of the control variable
@T{@rep{var}}, @T{@rep{limit}}, and @T{@rep{step}} are invisible variables. during the loop.
The names shown here are for explanatory purposes only.
}
@item{
If the third expression (the step) is absent,
then a step @N{of 1} is used.
}
@item{
You can use @Rw{break} and @Rw{goto} to exit a @Rw{for} loop.
}
@item{
The loop variable @T{v} is local to the loop body.
If you need its value after the loop, If you need its value after the loop,
assign it to another variable before exiting the loop. assign it to another variable before exiting the loop.
}
@item{
The values in @rep{var}, @rep{limit}, and @rep{step}
can be integers or floats.
All operations on them respect the usual rules in Lua.
}
} }
@sect4{@title{The generic @Rw{for} loop}
The generic @Rw{for} statement works over functions, The generic @Rw{for} statement works over functions,
called @def{iterators}. called @def{iterators}.
On each iteration, the iterator function is called to produce a new value, On each iteration, the iterator function is called to produce a new value,
@ -1499,6 +1475,8 @@ then assign them to other variables before breaking or exiting the loop.
} }
}
@sect3{funcstat| @title{Function Calls as Statements} @sect3{funcstat| @title{Function Calls as Statements}
To allow possible side-effects, To allow possible side-effects,
function calls can be executed as statements: function calls can be executed as statements:
@ -1819,7 +1797,7 @@ A comparison @T{a > b} is translated to @T{b < a}
and @T{a >= b} is translated to @T{b <= a}. and @T{a >= b} is translated to @T{b <= a}.
Following the @x{IEEE 754} standard, Following the @x{IEEE 754} standard,
@x{NaN} is considered neither smaller than, @x{NaN} is considered neither less than,
nor equal to, nor greater than any value (including itself). nor equal to, nor greater than any value (including itself).
} }
@ -2171,7 +2149,7 @@ then the function returns with no results.
@index{multiple return} @index{multiple return}
There is a system-dependent limit on the number of values There is a system-dependent limit on the number of values
that a function may return. that a function may return.
This limit is guaranteed to be larger than 1000. This limit is guaranteed to be greater than 1000.
The @emphx{colon} syntax The @emphx{colon} syntax
is used for defining @def{methods}, is used for defining @def{methods},
@ -2367,7 +2345,7 @@ but it also can be any positive index after the stack top
within the space allocated for the stack, within the space allocated for the stack,
that is, indices up to the stack size. that is, indices up to the stack size.
(Note that 0 is never an acceptable index.) (Note that 0 is never an acceptable index.)
Indices to upvalues @see{c-closure} larger than the real number Indices to upvalues @see{c-closure} greater than the real number
of upvalues in the current @N{C function} are also acceptable (but invalid). of upvalues in the current @N{C function} are also acceptable (but invalid).
Except when noted otherwise, Except when noted otherwise,
functions in the API work with acceptable indices. functions in the API work with acceptable indices.
@ -2879,7 +2857,7 @@ Ensures that the stack has space for at least @id{n} extra slots
(that is, that you can safely push up to @id{n} values into it). (that is, that you can safely push up to @id{n} values into it).
It returns false if it cannot fulfill the request, It returns false if it cannot fulfill the request,
either because it would cause the stack either because it would cause the stack
to be larger than a fixed maximum size to be greater than a fixed maximum size
(typically at least several thousand elements) or (typically at least several thousand elements) or
because it cannot allocate memory for the extra space. because it cannot allocate memory for the extra space.
This function never shrinks the stack; This function never shrinks the stack;
@ -4053,7 +4031,7 @@ for the @Q{newindex} event @see{metatable}.
Accepts any index, @N{or 0}, Accepts any index, @N{or 0},
and sets the stack top to this index. and sets the stack top to this index.
If the new top is larger than the old one, If the new top is greater than the old one,
then the new elements are filled with @nil. then the new elements are filled with @nil.
If @id{index} @N{is 0}, then all stack elements are removed. If @id{index} @N{is 0}, then all stack elements are removed.
@ -5056,7 +5034,7 @@ size @id{sz} with a call @T{luaL_buffinitsize(L, &b, sz)}.}
@item{ @item{
Finish by calling @T{luaL_pushresultsize(&b, sz)}, Finish by calling @T{luaL_pushresultsize(&b, sz)},
where @id{sz} is the total size of the resulting string where @id{sz} is the total size of the resulting string
copied into that space (which may be smaller than or copied into that space (which may be less than or
equal to the preallocated size). equal to the preallocated size).
} }
@ -7336,7 +7314,7 @@ Functions that interpret byte sequences only accept
valid sequences (well formed and not overlong). valid sequences (well formed and not overlong).
By default, they only accept byte sequences By default, they only accept byte sequences
that result in valid Unicode code points, that result in valid Unicode code points,
rejecting values larger than @T{10FFFF} and surrogates. rejecting values greater than @T{10FFFF} and surrogates.
A boolean argument @id{nonstrict}, when available, A boolean argument @id{nonstrict}, when available,
lifts these checks, lifts these checks,
so that all values up to @T{0x7FFFFFFF} are accepted. so that all values up to @T{0x7FFFFFFF} are accepted.
@ -7572,7 +7550,7 @@ returns the arc tangent of @id{y}.
@LibEntry{math.ceil (x)| @LibEntry{math.ceil (x)|
Returns the smallest integral value larger than or equal to @id{x}. Returns the smallest integral value greater than or equal to @id{x}.
} }
@ -7597,7 +7575,7 @@ Returns the value @M{e@sp{x}}
@LibEntry{math.floor (x)| @LibEntry{math.floor (x)|
Returns the largest integral value smaller than or equal to @id{x}. Returns the largest integral value less than or equal to @id{x}.
} }
@ -7611,7 +7589,7 @@ that rounds the quotient towards zero. (integer/float)
@LibEntry{math.huge| @LibEntry{math.huge|
The float value @idx{HUGE_VAL}, The float value @idx{HUGE_VAL},
a value larger than any other numeric value. a value greater than any other numeric value.
} }
@ -8352,7 +8330,7 @@ of the given thread:
@N{level 1} is the function that called @id{getinfo} @N{level 1} is the function that called @id{getinfo}
(except for tail calls, which do not count on the stack); (except for tail calls, which do not count on the stack);
and so on. and so on.
If @id{f} is a number larger than the number of active functions, If @id{f} is a number greater than the number of active functions,
then @id{getinfo} returns @nil. then @id{getinfo} returns @nil.
The returned table can contain all the fields returned by @Lid{lua_getinfo}, The returned table can contain all the fields returned by @Lid{lua_getinfo},
@ -8745,6 +8723,12 @@ has been removed.
When needed, this metamethod must be explicitly defined. When needed, this metamethod must be explicitly defined.
} }
@item{
The semantics of the numerical @Rw{for} loop
over integers changed in some details.
In particular, the control variable never wraps around.
}
@item{ @item{
When a coroutine finishes with an error, When a coroutine finishes with an error,
its stack is unwound (to run any pending closing methods). its stack is unwound (to run any pending closing methods).

View File

@ -303,9 +303,9 @@ check(function (x) return x & 2.0 end, 'LOADF', 'BAND', 'RETURN1')
-- basic 'for' loops -- basic 'for' loops
check(function () for i = -10, 10.5 do end end, check(function () for i = -10, 10.5 do end end,
'LOADI', 'LOADK', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0') 'LOADI', 'LOADK', 'LOADI', 'FORPREP', 'FORLOOP', 'RETURN0')
check(function () for i = 0xfffffff, 10.0, 1 do end end, check(function () for i = 0xfffffff, 10.0, 1 do end end,
'LOADK', 'LOADF', 'LOADI', 'FORPREP1', 'FORLOOP1', 'RETURN0') 'LOADK', 'LOADF', 'LOADI', 'FORPREP', 'FORLOOP', 'RETURN0')
-- bug in constant folding for 5.1 -- bug in constant folding for 5.1
check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN1') check(function () return -nil end, 'LOADNIL', 'UNM', 'RETURN1')

View File

@ -162,7 +162,7 @@ test([[for i,v in pairs{'a','b'} do
end end
]], {1,2,1,2,1,3}) ]], {1,2,1,2,1,3})
test([[for i=1,4 do a=1 end]], {1,1,1,1,1}) test([[for i=1,4 do a=1 end]], {1,1,1,1}, true)
do -- testing line info/trace with large gaps in source do -- testing line info/trace with large gaps in source

View File

@ -601,6 +601,69 @@ do -- checking types
end end
do -- testing other strange cases for numeric 'for'
local function checkfor (from, to, step, t)
local c = 0
for i = from, to, step do
c = c + 1
assert(i == t[c])
end
assert(c == #t)
end
local maxi = math.maxinteger
local mini = math.mininteger
checkfor(mini, maxi, maxi, {mini, -1, maxi - 1})
checkfor(mini, math.huge, maxi, {mini, -1, maxi - 1})
checkfor(maxi, mini, mini, {maxi, -1})
checkfor(maxi, mini, -maxi, {maxi, 0, -maxi})
checkfor(maxi, -math.huge, mini, {maxi, -1})
checkfor(maxi, mini, 1, {})
checkfor(mini, maxi, -1, {})
checkfor(maxi - 6, maxi, 3, {maxi - 6, maxi - 3, maxi})
checkfor(mini + 4, mini, -2, {mini + 4, mini + 2, mini})
local step = maxi // 10
local c = mini
for i = mini, maxi, step do
assert(i == c)
c = c + step
end
c = maxi
for i = maxi, mini, -step do
assert(i == c)
c = c - step
end
checkfor(maxi, maxi, maxi, {maxi})
checkfor(maxi, maxi, mini, {maxi})
checkfor(mini, mini, maxi, {mini})
checkfor(mini, mini, mini, {mini})
end
checkerror("'for' step is zero", function ()
for i = 1, 10, 0 do end
end)
checkerror("'for' step is zero", function ()
for i = 1, -10, 0 do end
end)
checkerror("'for' step is zero", function ()
for i = 1.0, -10, 0.0 do end
end)
collectgarbage() collectgarbage()