[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: [PATCH] Proposal: asynchronous coroutines and multithreading
- From: mniip <14@...>
- Date: Sat, 03 Jan 2015 10:06:59 +0300
Everyone keeps saying it's impossible to use a single lua_State from multiple
threads because the struct stores the instruction pointer and all that stuff.
Lanes works around this by creating multiple unrelated states. But I've
wondered, why create unrelated states when there's a built-in way of creating
states that are related, have the same environment and GC system: coroutines.
So I've worked on that idea and implemented threading as a proof of concept. The
attached patch, also available on github[1] introduces asynchronous coroutines:
every living coroutine (that is, not dead) can now be resumed asynchronously, in
a separate thread, and such asynchronously resumed coroutines can be joined
back. The coroutine can use main thread's objects, values, etc. Joined
coroutines aren't different from usual coroutines in any way, they can be
resumed (synchronously), lua_call'ed, etc.
The patch introduces 3 new C functions:
- int lua_threadstatus(lua_State *L)
Tells whether a coroutine is running in a thread (LUA_ASYNC), has
yielded/finished and can be joined (LUA_JOINABLE), or isn't running in a
thread at all (LUA_OK).
- int lua_async(lua_State *L, lua_State *from, int narg)
Acts just like lua_resume except it returns immediately and the coroutine is
resumed asynchronously.
- int lua_join(lua_State *L)
Waits until an asynchronously running coroutine returns or yields. The values
can be accessed the same way as after a lua_resume call.
Also introduces 2 new lua functions:
- coroutine.async(c, ...)
Acts just like coroutine.resume except it returns immediately and coroutine is
resumed asynchronously.
- coroutine.join(c)
Waits until an asynchronously running coroutine returns or yields, and
returns the returned/yielded values.
Also coroutine.status now returns "async" and "joinable" for respective statuses
of coroutines. Asynchronous coroutines are marked at the beginning of the GC
cycle so it's safe to throw away the coroutine reference after async'ing it,
note that coroutines are collectable again once they enter the joinable state
(and you'll lose any yielded/returned values).
All of this is built with pthreads, using python-style Global Interpreter Lock
(lua_lock and lua_unlock helped a lot in the process). Someone with proper
knowledge of the lua runtime and internals (unlike me) would probably be able to
rewrite the thing using per-state mutexes in a lot of places
There isn't any real reason lua shouldn't support threads like these.
- New dependency? Pthreads are a part of POSIX, and surely it won't be too hard
to port to That One Platform No One Likes.
- No one likes unnecessary complications, new types, new fundamental concepts?
This thing reuses an existing concept - coroutines.
- Performance impact? If threads aren't used, no unnecessary code is ever
executed so there's no slow down. If they are, as of right now the slowdown
is only about 5%, and if rewritten to use per-state mutexes it would approach
zero.
P. S. The reason I got interested in this idea is because I noticed that the
sets of good programming languages, easy to use programming languages, and
programming languages supporting sharing resources between threads, do not
intersect.
[1] https://gist.github.com/mniip/8dab452a3adba3c70fe7
--
/* mniip */
diff --git a/lua-5.2.3/src/Makefile b/lua-5.2.3/src/Makefile
index 7b4b2b7..efe504b 100644
--- a/lua-5.2.3/src/Makefile
+++ b/lua-5.2.3/src/Makefile
@@ -103,7 +103,7 @@ freebsd:
generic: $(ALL)
linux:
- $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline"
+ $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline -lpthread"
macosx:
$(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_MACOSX" SYSLIBS="-lreadline" CC=cc
@@ -144,7 +144,7 @@ ldo.o: ldo.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h ltm.h \
lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h \
lstring.h ltable.h lundump.h lvm.h
ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \
- lzio.h lmem.h lundump.h
+ lzio.h lmem.h lundump.h ldo.h
lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h \
lstate.h ltm.h lzio.h lmem.h
lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \
@@ -177,11 +177,11 @@ ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \
lmem.h lstring.h lgc.h ltable.h
lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h
luac.o: luac.c lua.h luaconf.h lauxlib.h lobject.h llimits.h lstate.h \
- ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h
+ ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h ldo.h
lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \
llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h
lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \
lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h
lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \
- lzio.h
+ lzio.h ldo.h
diff --git a/lua-5.2.3/src/lapi.c b/lua-5.2.3/src/lapi.c
index d011431..2da4a51 100644
--- a/lua-5.2.3/src/lapi.c
+++ b/lua-5.2.3/src/lapi.c
@@ -1014,6 +1014,11 @@ LUA_API int lua_status (lua_State *L) {
}
+LUA_API int lua_threadstatus (lua_State *L) {
+ return L->inthread;
+}
+
+
/*
** Garbage-collection function
*/
diff --git a/lua-5.2.3/src/lcorolib.c b/lua-5.2.3/src/lcorolib.c
index ce4f6ad..b4f5bc8 100644
--- a/lua-5.2.3/src/lcorolib.c
+++ b/lua-5.2.3/src/lcorolib.c
@@ -64,6 +64,73 @@ static int luaB_coresume (lua_State *L) {
}
+static int luaB_coasync (lua_State *L) {
+ lua_State *co = lua_tothread(L, 1);
+ int narg = lua_gettop(L) - 1;
+ luaL_argcheck(L, co, 1, "coroutine expected");
+ if (!lua_checkstack(co, narg)) {
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "too many arguments to resume");
+ return 2;
+ }
+ lua_xmove(L, co, narg);
+ switch(lua_async(co, L, narg)) {
+ case LUA_OK:
+ lua_pushboolean(L, 1);
+ return 1;
+ case LUA_ERRRUN:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "could not resume dead coroutine");
+ return 2;
+ case LUA_ASYNC:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "coroutine already running");
+ return 2;
+ default:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "could not create an asynchronous thread");
+ return 2;
+ }
+}
+
+static int luaB_cojoin (lua_State *L) {
+ lua_State *co = lua_tothread(L, 1);
+ luaL_argcheck(L, co, 1, "coroutine expected");
+ lua_settop(L, 0);
+ switch(lua_join(co)) {
+ case LUA_OK: case LUA_YIELD:
+ {
+ int nres = lua_gettop(co);
+ if (!lua_checkstack(L, nres + 1)) {
+ lua_pop(co, nres);
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "too many results to resume");
+ return 2;
+ }
+ lua_pushboolean(L, 1);
+ lua_xmove(co, L, nres);
+ return nres + 1;
+ }
+ case LUA_ASYNC:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "coroutine not joinable");
+ return 2;
+ case LUA_ERRRUN:
+ lua_pushboolean(L, 0);
+ lua_xmove(co, L, 1);
+ return 2;
+ case LUA_NOTHREAD:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "could not join coroutine");
+ return 2;
+ default:
+ lua_pushboolean(L, 0);
+ lua_pushliteral(L, "moo");
+ return 2;
+ }
+}
+
+
static int luaB_auxwrap (lua_State *L) {
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
int r = auxresume(L, co, lua_gettop(L));
@@ -104,6 +171,14 @@ static int luaB_yield (lua_State *L) {
static int luaB_costatus (lua_State *L) {
lua_State *co = lua_tothread(L, 1);
luaL_argcheck(L, co, 1, "coroutine expected");
+ switch(lua_threadstatus(co)) {
+ case LUA_ASYNC:
+ lua_pushliteral(L, "async");
+ return 1;
+ case LUA_JOINABLE:
+ lua_pushliteral(L, "joinable");
+ return 1;
+ }
if (L == co) lua_pushliteral(L, "running");
else {
switch (lua_status(co)) {
@@ -137,7 +212,9 @@ static int luaB_corunning (lua_State *L) {
static const luaL_Reg co_funcs[] = {
+ {"async", luaB_coasync},
{"create", luaB_cocreate},
+ {"join", luaB_cojoin},
{"resume", luaB_coresume},
{"running", luaB_corunning},
{"status", luaB_costatus},
diff --git a/lua-5.2.3/src/ldo.c b/lua-5.2.3/src/ldo.c
index e9dd5fa..89ddd9c 100644
--- a/lua-5.2.3/src/ldo.c
+++ b/lua-5.2.3/src/ldo.c
@@ -592,6 +592,107 @@ LUA_API int lua_yieldk (lua_State *L, int nresults, int ctx, lua_CFunction k) {
}
+#if defined(LUA_USE_PTHREAD)
+struct async_context { lua_State *L; lua_State *from; int nargs; };
+
+static void *auxasync(void *p)
+{
+ struct async_context *ctx = cast(struct async_context *, p);
+ lua_State *L = ctx->L;
+ lua_State *from = ctx->from;
+ int nargs = ctx->nargs;
+ int ret;
+ lua_lock(L);
+ luaM_free(L, ctx);
+ lua_unlock(L);
+ ret = lua_resume(L, from, nargs);
+ lua_lock(L);
+ L->inthread = LUA_JOINABLE;
+ L->threadret = ret;
+ G(L)->threads--;
+ luaD_gilunlock(L);
+ return NULL;
+}
+#endif
+
+LUA_API int lua_async (lua_State *L, lua_State *from, int nargs)
+{
+#if defined(LUA_USE_PTHREAD)
+ struct async_context *ctx;
+ lua_lock(L);
+ if (L->inthread != LUA_OK)
+ {
+ lua_unlock(L);
+ return LUA_ASYNC;
+ }
+ if (lua_status(L) == LUA_OK && lua_gettop(L) == 0) {
+ lua_unlock(L);
+ return LUA_ERRRUN;
+ }
+ ctx = luaM_new(L, struct async_context);
+ ctx->L = L;
+ ctx->from = from;
+ ctx->nargs = nargs;
+ L->inthread = LUA_ASYNC;
+ if(!G(L)->threads)
+ luaD_gillock(L);
+ G(L)->threads++;
+ if (pthread_create(&L->thread, NULL, &auxasync, cast(void *, ctx)))
+ {
+ L->inthread = LUA_OK;
+ G(L)->threads--;
+ luaD_gilunlock(L);
+ return LUA_NOTHREAD;
+ }
+ lua_unlock(L);
+ return LUA_OK;
+#else
+ return LUA_NOTHREAD;
+#endif
+}
+
+int luaD_join (lua_State *L)
+{
+#if defined(LUA_USE_PTHREAD)
+ void *dummy;
+ int ret = pthread_join(L->thread, &dummy);
+ lua_lock(L);
+ L->inthread = LUA_OK;
+ lua_unlock(L);
+ return ret;
+#endif
+ return 1;
+}
+
+void luaD_cancel (lua_State *L)
+{
+#if defined(LUA_USE_PTHREAD)
+ lua_unlock(L);
+ pthread_cancel(L->thread);
+ luaD_join(L);
+ lua_lock(L);
+#endif
+}
+
+LUA_API int lua_join (lua_State *L)
+{
+#if defined(LUA_USE_PTHREAD)
+ lua_lock(L);
+ if(L->inthread == LUA_OK)
+ {
+ lua_unlock(L);
+ return LUA_ASYNC;
+ }
+ lua_unlock(L);
+ if(luaD_join(L))
+ return LUA_NOTHREAD;
+ return L->threadret;
+#else
+ return LUA_NOTHREAD;
+#endif
+}
+
+
int luaD_pcall (lua_State *L, Pfunc func, void *u,
ptrdiff_t old_top, ptrdiff_t ef) {
int status;
@@ -679,3 +780,18 @@ int luaD_protectedparser (lua_State *L, ZIO *z, const char *name,
}
+void luaD_gillock (lua_State *L)
+{
+#if defined(LUA_USE_PTHREAD)
+ pthread_mutex_lock(&G(L)->mutex);
+#endif
+}
+
+
+void luaD_gilunlock (lua_State *L)
+{
+#if defined(LUA_USE_PTHREAD)
+ pthread_mutex_unlock(&G(L)->mutex);
+ pthread_testcancel();
+#endif
+}
diff --git a/lua-5.2.3/src/ldo.h b/lua-5.2.3/src/ldo.h
index d3d3082..2717595 100644
--- a/lua-5.2.3/src/ldo.h
+++ b/lua-5.2.3/src/ldo.h
@@ -42,5 +42,10 @@ LUAI_FUNC void luaD_shrinkstack (lua_State *L);
LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode);
LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
+LUAI_FUNC int luaD_join (lua_State *L);
+LUAI_FUNC void luaD_cancel (lua_State *L);
+LUAI_FUNC void luaD_gillock (lua_State *L);
+LUAI_FUNC void luaD_gilunlock (lua_State *L);
+
#endif
diff --git a/lua-5.2.3/src/ldump.c b/lua-5.2.3/src/ldump.c
index 61fa2cd..b5b7604 100644
--- a/lua-5.2.3/src/ldump.c
+++ b/lua-5.2.3/src/ldump.c
@@ -14,6 +14,7 @@
#include "lobject.h"
#include "lstate.h"
#include "lundump.h"
+#include "ldo.h"
typedef struct {
lua_State* L;
diff --git a/lua-5.2.3/src/lgc.c b/lua-5.2.3/src/lgc.c
index 52460dc..25597fa 100644
--- a/lua-5.2.3/src/lgc.c
+++ b/lua-5.2.3/src/lgc.c
@@ -305,6 +305,32 @@ static void markmt (global_State *g) {
}
+static void markasync (global_State *g) {
+ if(g->threads)
+ {
+ GCObject *o;
+ for(o = g->allgc; o !=NULL; o = gch(o)->next)
+ if(gch(o)->tt == LUA_TTHREAD && gco2th(o)->inthread == LUA_ASYNC)
+ markobject(g, o);
+ }
+}
+
+
+static void cancelasync (global_State *g) {
+ tryagain:
+ if(g->threads)
+ {
+ GCObject *o;
+ for(o = g->allgc; o !=NULL; o = gch(o)->next)
+ if(gch(o)->tt == LUA_TTHREAD && gco2th(o)->inthread == LUA_ASYNC)
+ {
+ luaD_cancel(gco2th(o));
+ goto tryagain;
+ }
+ }
+}
+
+
/*
** mark all objects in list of being-finalized
*/
@@ -338,6 +364,7 @@ static void restartcollection (global_State *g) {
g->gray = g->grayagain = NULL;
g->weak = g->allweak = g->ephemeron = NULL;
markobject(g, g->mainthread);
+ markasync(g);
markvalue(g, &g->l_registry);
markmt(g);
markbeingfnz(g); /* mark any finalizing object left from previous cycle */
@@ -986,6 +1013,7 @@ void luaC_freeallobjects (lua_State *L) {
separatetobefnz(L, 1); /* separate all objects with finalizers */
lua_assert(g->finobj == NULL);
callallpendingfinalizers(L, 0);
+ cancelasync(g);
g->currentwhite = WHITEBITS; /* this "white" makes all objects look dead */
g->gckind = KGC_NORMAL;
sweepwholelist(L, &g->finobj); /* finalizers can create objs. in 'finobj' */
@@ -1002,6 +1030,7 @@ static l_mem atomic (lua_State *L) {
GCObject *origweak, *origall;
lua_assert(!iswhite(obj2gco(g->mainthread)));
markobject(g, L); /* mark running thread */
+ markasync(g);
/* registry and global metatables may be changed by API */
markvalue(g, &g->l_registry);
markmt(g); /* mark basic metatables */
diff --git a/lua-5.2.3/src/llimits.h b/lua-5.2.3/src/llimits.h
index 152dd05..aae5e1f 100644
--- a/lua-5.2.3/src/llimits.h
+++ b/lua-5.2.3/src/llimits.h
@@ -152,9 +152,14 @@ typedef lu_int32 Instruction;
#if !defined(lua_lock)
+#if defined(LUA_USE_PTHREAD)
+#define lua_lock(L) ((void) (G(L)->threads && (luaD_gillock(L), 0)))
+#define lua_unlock(L) ((void) (G(L)->threads && (luaD_gilunlock(L), 0)))
+#else
#define lua_lock(L) ((void) 0)
#define lua_unlock(L) ((void) 0)
#endif
+#endif
#if !defined(luai_threadyield)
#define luai_threadyield(L) {lua_unlock(L); lua_lock(L);}
diff --git a/lua-5.2.3/src/lstate.c b/lua-5.2.3/src/lstate.c
index c7f2672..2636483 100644
--- a/lua-5.2.3/src/lstate.c
+++ b/lua-5.2.3/src/lstate.c
@@ -217,6 +217,7 @@ static void preinit_state (lua_State *L, global_State *g) {
L->nny = 1;
L->status = LUA_OK;
L->errfunc = 0;
+ L->inthread = LUA_OK;
}
@@ -229,6 +230,9 @@ static void close_state (lua_State *L) {
luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size);
luaZ_freebuffer(L, &g->buff);
freestack(L);
+#if defined(LUA_USE_PTHREAD)
+ pthread_mutex_destroy(&g->mutex);
+#endif
lua_assert(gettotalbytes(g) == sizeof(LG));
(*g->frealloc)(g->ud, fromstate(L), sizeof(LG), 0); /* free main block */
}
@@ -255,6 +259,11 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
void luaE_freethread (lua_State *L, lua_State *L1) {
LX *l = fromstate(L1);
+#if defined(LUA_USE_PTHREAD)
+ lua_assert(L1->inthread != LUA_ASYNC);
+ if(L1->inthread == LUA_JOINABLE)
+ luaD_join(L1);
+#endif
luaF_close(L1, L1->stack); /* close all upvalues for this thread */
lua_assert(L1->openupval == NULL);
luai_userstatefree(L, L1);
@@ -310,6 +319,10 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
close_state(L);
L = NULL;
}
+#if defined(LUA_USE_PTHREAD)
+ pthread_mutex_init(&g->mutex, NULL);
+ g->threads = 0;
+#endif
return L;
}
diff --git a/lua-5.2.3/src/lstate.h b/lua-5.2.3/src/lstate.h
index daffd9a..110d406 100644
--- a/lua-5.2.3/src/lstate.h
+++ b/lua-5.2.3/src/lstate.h
@@ -145,6 +145,10 @@ typedef struct global_State {
TString *memerrmsg; /* memory-error message */
TString *tmname[TM_N]; /* array with tag-method names */
struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
+ int threads;
+#if defined(LUA_USE_PTHREAD)
+ pthread_mutex_t mutex;
+#endif
} global_State;
@@ -173,6 +177,11 @@ struct lua_State {
struct lua_longjmp *errorJmp; /* current error recover point */
ptrdiff_t errfunc; /* current error handling function (stack index) */
CallInfo base_ci; /* CallInfo for first level (C calling Lua) */
+ int inthread;
+ int threadret;
+#if defined(LUA_USE_PTHREAD)
+ pthread_t thread;
+#endif
};
diff --git a/lua-5.2.3/src/lua.h b/lua-5.2.3/src/lua.h
index 149a2c3..a0dc697 100644
--- a/lua-5.2.3/src/lua.h
+++ b/lua-5.2.3/src/lua.h
@@ -49,6 +49,9 @@
#define LUA_ERRMEM 4
#define LUA_ERRGCMM 5
#define LUA_ERRERR 6
+#define LUA_ASYNC 7
+#define LUA_JOINABLE 8
+#define LUA_NOTHREAD 9
typedef struct lua_State lua_State;
@@ -273,6 +276,9 @@ LUA_API int (lua_yieldk) (lua_State *L, int nresults, int ctx,
#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL)
LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg);
LUA_API int (lua_status) (lua_State *L);
+LUA_API int (lua_async) (lua_State *L, lua_State *from, int narg);
+LUA_API int (lua_join) (lua_State *L);
+LUA_API int (lua_threadstatus) (lua_State *L);
/*
** garbage-collection function and options
diff --git a/lua-5.2.3/src/luac.c b/lua-5.2.3/src/luac.c
index 7409706..ff19ca3 100644
--- a/lua-5.2.3/src/luac.c
+++ b/lua-5.2.3/src/luac.c
@@ -18,6 +18,7 @@
#include "lobject.h"
#include "lstate.h"
#include "lundump.h"
+#include "ldo.h"
static void PrintFunction(const Proto* f, int full);
#define luaU_print PrintFunction
diff --git a/lua-5.2.3/src/luaconf.h b/lua-5.2.3/src/luaconf.h
index 18be9a9..c609a6d 100644
--- a/lua-5.2.3/src/luaconf.h
+++ b/lua-5.2.3/src/luaconf.h
@@ -47,6 +47,7 @@
#define LUA_USE_STRTODHEX /* assume 'strtod' handles hex formats */
#define LUA_USE_AFORMAT /* assume 'printf' handles 'aA' specifiers */
#define LUA_USE_LONGLONG /* assume support for long long */
+#define LUA_USE_PTHREAD
#endif
#if defined(LUA_USE_MACOSX)
@@ -74,6 +75,9 @@
#endif
+#if defined(LUA_USE_PTHREAD)
+#include <pthread.h>
+#endif
/*
@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for
diff --git a/lua-5.2.3/src/lvm.c b/lua-5.2.3/src/lvm.c
index 141b9fd..f15268e 100644
--- a/lua-5.2.3/src/lvm.c
+++ b/lua-5.2.3/src/lvm.c
@@ -536,6 +536,7 @@ void luaV_execute (lua_State *L) {
LClosure *cl;
TValue *k;
StkId base;
+ int yield = 0;
newframe: /* reentry point when frame changes (call/return) */
lua_assert(ci == L->ci);
cl = clLvalue(ci->func);
@@ -549,6 +550,11 @@ void luaV_execute (lua_State *L) {
(--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
Protect(traceexec(L));
}
+ if(!yield--)
+ {
+ yield = 64;
+ luai_threadyield(L);
+ }
/* WARNING: several calls may realloc the stack and invalidate `ra' */
ra = RA(i);
lua_assert(base == ci->u.l.base);
diff --git a/lua-5.2.3/src/lzio.c b/lua-5.2.3/src/lzio.c
index 20efea9..5c736fe 100644
--- a/lua-5.2.3/src/lzio.c
+++ b/lua-5.2.3/src/lzio.c
@@ -16,6 +16,7 @@
#include "lmem.h"
#include "lstate.h"
#include "lzio.h"
+#include "ldo.h"
int luaZ_fill (ZIO *z) {