Skip to content

Commit 3da34a5

Browse files
committed
Revamp of 'lua_pushfstring' / 'luaO_pushvfstring'
The function 'luaO_pushvfstring' now uses an internal buffer to concatenate small strings, instead of pushing all pieces on the stack. This avoids the creation of several small Lua strings for each piece of the result. (For instance, a format like "n: '%d'" used to create three intermediate strings: "n: '", the numeral, and "'". Now it creates none.)
1 parent 20b161e commit 3da34a5

4 files changed

Lines changed: 177 additions & 44 deletions

File tree

lobject.c

Lines changed: 107 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -364,101 +364,171 @@ int luaO_utf8esc (char *buff, unsigned long x) {
364364

365365

366366
/*
367-
** Convert a number object to a string
367+
** Convert a number object to a string, adding it to a buffer
368368
*/
369-
void luaO_tostring (lua_State *L, TValue *obj) {
370-
char buff[MAXNUMBER2STR];
369+
static size_t tostringbuff (TValue *obj, char *buff) {
371370
size_t len;
372371
lua_assert(ttisnumber(obj));
373372
if (ttisinteger(obj))
374-
len = lua_integer2str(buff, sizeof(buff), ivalue(obj));
373+
len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
375374
else {
376-
len = lua_number2str(buff, sizeof(buff), fltvalue(obj));
375+
len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));
377376
if (buff[strspn(buff, "-0123456789")] == '\0') { /* looks like an int? */
378377
buff[len++] = lua_getlocaledecpoint();
379378
buff[len++] = '0'; /* adds '.0' to result */
380379
}
381380
}
381+
return len;
382+
}
383+
384+
385+
/*
386+
** Convert a number object to a Lua string, replacing the value at 'obj'
387+
*/
388+
void luaO_tostring (lua_State *L, TValue *obj) {
389+
char buff[MAXNUMBER2STR];
390+
size_t len = tostringbuff(obj, buff);
382391
setsvalue(L, obj, luaS_newlstr(L, buff, len));
383392
}
384393

385394

395+
/* size for buffer used by 'luaO_pushvfstring' */
396+
#define BUFVFS 400
397+
398+
/* buffer used by 'luaO_pushvfstring' */
399+
typedef struct BuffFS {
400+
int blen; /* length of partial string in 'buff' */
401+
char buff[BUFVFS]; /* holds last part of the result */
402+
} BuffFS;
403+
404+
386405
static void pushstr (lua_State *L, const char *str, size_t l) {
387406
setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
388407
L->top++;
389408
}
390409

391410

411+
/*
412+
** empty the buffer into the stack
413+
*/
414+
static void clearbuff (lua_State *L, BuffFS *buff) {
415+
pushstr(L, buff->buff, buff->blen); /* push buffer */
416+
buff->blen = 0; /* buffer now is empty */
417+
}
418+
419+
420+
/*
421+
** Add 'str' to the buffer. It buffer has no enough space,
422+
** empty the buffer. If string is still larger than the buffer,
423+
** push the string directly to the stack. Return number of items
424+
** pushed.
425+
*/
426+
static int addstr2buff (lua_State *L, BuffFS *buff, const char *str,
427+
size_t slen) {
428+
int pushed = 0; /* number of items pushed to the stack */
429+
lua_assert(buff->blen <= BUFVFS);
430+
if (slen > BUFVFS - cast_sizet(buff->blen)) { /* string does not fit? */
431+
clearbuff(L, buff);
432+
pushed = 1;
433+
if (slen >= BUFVFS) { /* string still does not fit into buffer? */
434+
pushstr(L, str, slen); /* push string */
435+
return 2;
436+
}
437+
}
438+
memcpy(buff->buff + buff->blen, str, slen); /* add string to buffer */
439+
buff->blen += slen;
440+
return pushed;
441+
}
442+
443+
444+
/*
445+
** Add a number to the buffer; return number of strings pushed into
446+
** the stack. (At most one, to free buffer space.)
447+
*/
448+
static int addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
449+
char numbuff[MAXNUMBER2STR];
450+
size_t len = tostringbuff(num, numbuff); /* format number into 'numbuff' */
451+
return addstr2buff(L, buff, numbuff, len);
452+
}
453+
454+
392455
/*
393456
** this function handles only '%d', '%c', '%f', '%p', and '%s'
394457
conventional formats, plus Lua-specific '%I' and '%U'
395458
*/
396459
const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
397-
int n = 0; /* number of strings in the stack to concatenate */
398-
const char *e; /* points to next conversion specifier */
460+
BuffFS buff; /* holds last part of the result */
461+
int pushed = 0; /* number of strings in the stack to concatenate */
462+
const char *e; /* points to next '%' */
463+
buff.blen = 0;
399464
while ((e = strchr(fmt, '%')) != NULL) {
400-
pushstr(L, fmt, e - fmt); /* string up to conversion specifier */
401-
switch (*(e+1)) {
465+
pushed += addstr2buff(L, &buff, fmt, e - fmt); /* add 'fmt' up to '%' */
466+
switch (*(e + 1)) { /* conversion specifier */
402467
case 's': { /* zero-terminated string */
403468
const char *s = va_arg(argp, char *);
404469
if (s == NULL) s = "(null)";
405-
pushstr(L, s, strlen(s));
470+
pushed += addstr2buff(L, &buff, s, strlen(s));
406471
break;
407472
}
408473
case 'c': { /* an 'int' as a character */
409-
char buff = cast_char(va_arg(argp, int));
410-
if (lisprint(cast_uchar(buff)))
411-
pushstr(L, &buff, 1);
412-
else /* non-printable character; print its code */
413-
luaO_pushfstring(L, "<\\%d>", cast_uchar(buff));
474+
/* if non-printable character, print its code */
475+
char bf[10];
476+
int c = va_arg(argp, int);
477+
int l = (lisprint(c)) ? l_sprintf(bf, sizeof(bf), "%c", c)
478+
: l_sprintf(bf, sizeof(bf), "<\\%u>", c);
479+
pushed += addstr2buff(L, &buff, bf, l);
414480
break;
415481
}
416482
case 'd': { /* an 'int' */
417-
setivalue(s2v(L->top), va_arg(argp, int));
418-
goto top2str;
483+
TValue num;
484+
setivalue(&num, va_arg(argp, int));
485+
pushed += addnum2buff(L, &buff, &num);
486+
break;
419487
}
420488
case 'I': { /* a 'lua_Integer' */
421-
setivalue(s2v(L->top), cast(lua_Integer, va_arg(argp, l_uacInt)));
422-
goto top2str;
489+
TValue num;
490+
setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
491+
pushed += addnum2buff(L, &buff, &num);
492+
break;
423493
}
424494
case 'f': { /* a 'lua_Number' */
425-
setfltvalue(s2v(L->top), cast_num(va_arg(argp, l_uacNumber)));
426-
top2str: /* convert the top element to a string */
427-
L->top++;
428-
luaO_tostring(L, s2v(L->top - 1));
495+
TValue num;
496+
setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
497+
pushed += addnum2buff(L, &buff, &num);
429498
break;
430499
}
431500
case 'p': { /* a pointer */
432-
char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */
501+
char bf[3 * sizeof(void*) + 8]; /* should be enough space for '%p' */
433502
void *p = va_arg(argp, void *);
434-
int l = lua_pointer2str(buff, sizeof(buff), p);
435-
pushstr(L, buff, l);
503+
int l = l_sprintf(bf, sizeof(bf), "%p", p);
504+
pushed += addstr2buff(L, &buff, bf, l);
436505
break;
437506
}
438507
case 'U': { /* a 'long' as a UTF-8 sequence */
439-
char buff[UTF8BUFFSZ];
440-
int l = luaO_utf8esc(buff, va_arg(argp, long));
441-
pushstr(L, buff + UTF8BUFFSZ - l, l);
508+
char bf[UTF8BUFFSZ];
509+
int l = luaO_utf8esc(bf, va_arg(argp, long));
510+
pushed += addstr2buff(L, &buff, bf + UTF8BUFFSZ - l, l);
442511
break;
443512
}
444513
case '%': {
445-
pushstr(L, "%", 1);
514+
pushed += addstr2buff(L, &buff, "%", 1);
446515
break;
447516
}
448517
default: {
449518
luaG_runerror(L, "invalid option '%%%c' to 'lua_pushfstring'",
450519
*(e + 1));
451520
}
452521
}
453-
n += 2;
454-
if (L->top + 2 > L->stack_last) { /* no free stack space? */
455-
luaV_concat(L, n);
456-
n = 1;
522+
if (pushed > 1 && L->top + 2 > L->stack_last) { /* no free stack space? */
523+
luaV_concat(L, pushed); /* join all partial results into one */
524+
pushed = 1;
457525
}
458-
fmt = e + 2;
526+
fmt = e + 2; /* skip '%' and the specifier */
459527
}
460-
pushstr(L, fmt, strlen(fmt));
461-
if (n > 0) luaV_concat(L, n + 1);
528+
pushed += addstr2buff(L, &buff, fmt, strlen(fmt)); /* rest of 'fmt' */
529+
clearbuff(L, &buff); /* empty buffer into the stack */
530+
if (pushed > 0)
531+
luaV_concat(L, pushed + 1); /* join all partial results */
462532
return svalue(s2v(L->top - 1));
463533
}
464534

ltests.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,15 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
14811481
else if EQ("pushvalue") {
14821482
lua_pushvalue(L1, getindex);
14831483
}
1484+
else if EQ("pushfstringI") {
1485+
lua_pushfstring(L1, lua_tostring(L, -2), (int)lua_tointeger(L, -1));
1486+
}
1487+
else if EQ("pushfstringS") {
1488+
lua_pushfstring(L1, lua_tostring(L, -2), lua_tostring(L, -1));
1489+
}
1490+
else if EQ("pushfstringP") {
1491+
lua_pushfstring(L1, lua_tostring(L, -2), lua_topointer(L, -1));
1492+
}
14841493
else if EQ("rawgeti") {
14851494
int t = getindex;
14861495
lua_rawgeti(L1, t, getnum);

luaconf.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -578,13 +578,6 @@
578578
#endif
579579

580580

581-
/*
582-
@@ lua_pointer2str converts a pointer to a readable string in a
583-
** non-specified way.
584-
*/
585-
#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p)
586-
587-
588581
/*
589582
@@ lua_number2strx converts a float to a hexadecimal numeric string.
590583
** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that.

testes/strings.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,5 +400,66 @@ do
400400
assert(co() == "2")
401401
end
402402

403+
404+
if T==nil then
405+
(Message or print)
406+
("\n >>> testC not active: skipping 'pushfstring' tests <<<\n")
407+
else
408+
409+
print"testing 'pushfstring'"
410+
411+
-- formats %U, %f, %I already tested elsewhere
412+
413+
local blen = 400 -- internal buffer length in 'luaO_pushfstring'
414+
415+
local function callpfs (op, fmt, n)
416+
local x = {T.testC("pushfstring" .. op .. "; return *", fmt, n)}
417+
-- stack has code, 'fmt', 'n', and result from operation
418+
assert(#x == 4) -- make sure nothing else was left in the stack
419+
return x[4]
420+
end
421+
422+
local function testpfs (op, fmt, n)
423+
assert(callpfs(op, fmt, n) == string.format(fmt, n))
424+
end
425+
426+
testpfs("I", "", 0)
427+
testpfs("I", string.rep("a", blen - 1), 0)
428+
testpfs("I", string.rep("a", blen), 0)
429+
testpfs("I", string.rep("a", blen + 1), 0)
430+
431+
local str = string.rep("ab", blen) .. "%d" .. string.rep("d", blen / 2)
432+
testpfs("I", str, 2^14)
433+
testpfs("I", str, -2^15)
434+
435+
str = "%d" .. string.rep("cd", blen)
436+
testpfs("I", str, 2^14)
437+
testpfs("I", str, -2^15)
438+
439+
str = string.rep("c", blen - 2) .. "%d"
440+
testpfs("I", str, 2^14)
441+
testpfs("I", str, -2^15)
442+
443+
for l = 12, 14 do
444+
local str1 = string.rep("a", l)
445+
for i = 0, 500, 13 do
446+
for j = 0, 500, 13 do
447+
str = string.rep("a", i) .. "%s" .. string.rep("d", j)
448+
testpfs("S", str, str1)
449+
testpfs("S", str, str)
450+
end
451+
end
452+
end
453+
454+
str = "abc %c def"
455+
testpfs("I", str, string.byte("A"))
456+
-- non-printable character
457+
assert(callpfs("I", str, 255) == "abc <\\255> def")
458+
459+
str = string.rep("a", blen - 1) .. "%p" .. string.rep("cd", blen)
460+
testpfs("P", str, {})
461+
end
462+
463+
403464
print('OK')
404465

0 commit comments

Comments
 (0)