From 83a37aeca74724ef76dee7c8246bdbb88132940d Mon Sep 17 00:00:00 2001 From: Mike Pall Date: Mon, 28 Feb 2011 16:48:13 +0100 Subject: [PATCH] FFI: Add ffi.gc() function for finalization of cdata objects. --- doc/ext_ffi_api.html | 28 ++++++++++++ src/Makefile.dep | 10 ++--- src/lib_ffi.c | 77 ++++++++++++++++++++++++-------- src/lj_cdata.c | 13 +++++- src/lj_ctype.c | 3 +- src/lj_ctype.h | 3 +- src/lj_gc.c | 103 +++++++++++++++++++++++++++++++------------ src/lj_gc.h | 8 +++- src/lj_state.c | 3 +- 9 files changed, 191 insertions(+), 57 deletions(-) diff --git a/doc/ext_ffi_api.html b/doc/ext_ffi_api.html index b8c52fb6..5bd4b80c 100644 --- a/doc/ext_ffi_api.html +++ b/doc/ext_ffi_api.html @@ -238,6 +238,34 @@ This functions is mainly useful to override the pointer compatibility checks or to convert pointers to addresses or vice versa.

+

cdata = ffi.gc(cdata, finalizer)

+

+Associates a finalizer with a pointer or aggregate cdata object. The +cdata object is returned unchanged. +

+

+This function allows safe integration of unmanaged resources into the +automatic memory management of the LuaJIT garbage collector. Typical +usage: +

+
+local p = ffi.gc(ffi.C.malloc(n), ffi.C.free)
+...
+p = nil -- Last reference to p is gone.
+-- GC will eventually run finalizer: ffi.C.free(p)
+
+

+A cdata finalizer works like the __gc metamethod for userdata +objects: when the last reference to a cdata object is gone, the +associated finalizer is called with the cdata object as an argument. The +finalizer can be a Lua function or a cdata function or cdata function +pointer. An existing finalizer can be removed by setting a nil +finalizer, e.g. right before explicitly deleting a resource: +

+
+ffi.C.free(ffi.gc(p, nil)) -- Manually free the memory.
+
+

C Type Information

The following API functions return information about C types. diff --git a/src/Makefile.dep b/src/Makefile.dep index 7f87155e..1684ebd7 100644 --- a/src/Makefile.dep +++ b/src/Makefile.dep @@ -21,9 +21,9 @@ lib_bit.o: lib_bit.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \ lib_debug.o: lib_debug.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h \ lj_def.h lj_arch.h lj_err.h lj_errmsg.h lj_lib.h lj_libdef.h lib_ffi.o: lib_ffi.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \ - lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_str.h lj_ctype.h lj_cparse.h \ - lj_cdata.h lj_cconv.h lj_carith.h lj_ccall.h lj_clib.h lj_ff.h \ - lj_ffdef.h lj_lib.h lj_libdef.h + lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_ctype.h \ + lj_cparse.h lj_cdata.h lj_cconv.h lj_carith.h lj_ccall.h lj_clib.h \ + lj_ff.h lj_ffdef.h lj_lib.h lj_libdef.h lib_init.o: lib_init.c lua.h luaconf.h lauxlib.h lualib.h lj_arch.h lib_io.o: lib_io.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \ lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_str.h lj_ff.h lj_ffdef.h \ @@ -96,7 +96,7 @@ lj_func.o: lj_func.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \ lj_traceerr.h lj_vm.h lj_gc.o: lj_gc.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \ lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_func.h lj_udata.h lj_meta.h \ - lj_state.h lj_frame.h lj_bc.h lj_cdata.h lj_ctype.h lj_trace.h lj_jit.h \ + lj_state.h lj_frame.h lj_bc.h lj_ctype.h lj_cdata.h lj_trace.h lj_jit.h \ lj_ir.h lj_dispatch.h lj_traceerr.h lj_vm.h lj_gdbjit.o: lj_gdbjit.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ lj_gc.h lj_err.h lj_errmsg.h lj_frame.h lj_bc.h lj_jit.h lj_ir.h \ @@ -161,7 +161,7 @@ lj_vmevent.o: lj_vmevent.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ lj_vm.h lj_vmevent.h ljamalg.o: ljamalg.c lua.h luaconf.h lauxlib.h lj_gc.c lj_obj.h lj_def.h \ lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_func.h \ - lj_udata.h lj_meta.h lj_state.h lj_frame.h lj_bc.h lj_cdata.h lj_ctype.h \ + lj_udata.h lj_meta.h lj_state.h lj_frame.h lj_bc.h lj_ctype.h lj_cdata.h \ lj_trace.h lj_jit.h lj_ir.h lj_dispatch.h lj_traceerr.h lj_vm.h lj_err.c \ lj_ff.h lj_ffdef.h lj_char.c lj_char.h lj_bc.c lj_bcdef.h lj_obj.c \ lj_str.c lj_tab.c lj_func.c lj_udata.c lj_meta.c lj_state.c lj_lex.h \ diff --git a/src/lib_ffi.c b/src/lib_ffi.c index e88b6f54..674bbf00 100644 --- a/src/lib_ffi.c +++ b/src/lib_ffi.c @@ -17,6 +17,7 @@ #include "lj_gc.h" #include "lj_err.h" #include "lj_str.h" +#include "lj_tab.h" #include "lj_ctype.h" #include "lj_cparse.h" #include "lj_cdata.h" @@ -353,6 +354,24 @@ LJLIB_CF(ffi_new) LJLIB_REC(.) return 1; } +LJLIB_CF(ffi_cast) LJLIB_REC(ffi_new) +{ + CTState *cts = ctype_cts(L); + CTypeID id = ffi_checkctype(L, cts); + CType *d = ctype_raw(cts, id); + TValue *o = lj_lib_checkany(L, 2); + L->top = o+1; /* Make sure this is the last item on the stack. */ + if (!(ctype_isnum(d->info) || ctype_isptr(d->info) || ctype_isenum(d->info))) + lj_err_arg(L, 1, LJ_ERR_FFI_INVTYPE); + if (!(tviscdata(o) && cdataV(o)->typeid == id)) { + GCcdata *cd = lj_cdata_new(cts, id, d->size); + lj_cconv_ct_tv(cts, d, cdataptr(cd), o, CCF_CAST); + setcdataV(L, o, cd); + lj_gc_check(L); + } + return 1; +} + LJLIB_CF(ffi_typeof) { CTState *cts = ctype_cts(L); @@ -419,24 +438,6 @@ LJLIB_CF(ffi_offsetof) return 0; } -LJLIB_CF(ffi_cast) LJLIB_REC(ffi_new) -{ - CTState *cts = ctype_cts(L); - CTypeID id = ffi_checkctype(L, cts); - CType *d = ctype_raw(cts, id); - TValue *o = lj_lib_checkany(L, 2); - L->top = o+1; /* Make sure this is the last item on the stack. */ - if (!(ctype_isnum(d->info) || ctype_isptr(d->info) || ctype_isenum(d->info))) - lj_err_arg(L, 1, LJ_ERR_FFI_INVTYPE); - if (!(tviscdata(o) && cdataV(o)->typeid == id)) { - GCcdata *cd = lj_cdata_new(cts, id, d->size); - lj_cconv_ct_tv(cts, d, cdataptr(cd), o, CCF_CAST); - setcdataV(L, o, cd); - lj_gc_check(L); - } - return 1; -} - LJLIB_CF(ffi_string) LJLIB_REC(.) { CTState *cts = ctype_cts(L); @@ -520,6 +521,30 @@ LJLIB_CF(ffi_abi) LJLIB_REC(.) #undef H_ +LJLIB_PUSH(top-7) LJLIB_SET(!) /* Store reference to weak table. */ + +LJLIB_CF(ffi_gc) +{ + GCcdata *cd = ffi_checkcdata(L, 1); + TValue *fin = lj_lib_checkany(L, 2); + CTState *cts = ctype_cts(L); + GCtab *t = cts->finalizer; + CType *ct = ctype_raw(cts, cd->typeid); + if (!(ctype_isptr(ct->info) || ctype_isstruct(ct->info) || + ctype_isrefarray(ct->info))) + lj_err_arg(L, 1, LJ_ERR_FFI_INVTYPE); + if (gcref(t->metatable)) { /* Update finalizer table, if still enabled. */ + copyTV(L, lj_tab_set(L, t, L->base), fin); + lj_gc_anybarriert(L, t); + if (!tvisnil(fin)) + cd->marked |= LJ_GC_CDATA_FIN; + else + cd->marked &= ~LJ_GC_CDATA_FIN; + } + L->top = L->base+1; /* Pass through the cdata object. */ + return 1; +} + LJLIB_PUSH(top-5) LJLIB_SET(!) /* Store clib metatable in func environment. */ LJLIB_CF(ffi_load) @@ -538,9 +563,23 @@ LJLIB_PUSH(top-2) LJLIB_SET(arch) /* ------------------------------------------------------------------------ */ +/* Create special weak-keyed finalizer table. */ +static GCtab *ffi_finalizer(lua_State *L) +{ + /* NOBARRIER: The table is new (marked white). */ + GCtab *t = lj_tab_new(L, 0, 1); + settabV(L, L->top++, t); + setgcref(t->metatable, obj2gco(t)); + setstrV(L, lj_tab_setstr(L, t, lj_str_newlit(L, "__mode")), + lj_str_newlit(L, "K")); + t->nomm = (uint8_t)(~(1u<finalizer = ffi_finalizer(L); LJ_LIB_REG(L, NULL, ffi_meta); /* NOBARRIER: basemt is a GC root. */ setgcref(basemt_it(G(L), LJ_TCDATA), obj2gco(tabV(L->top-1))); diff --git a/src/lj_cdata.c b/src/lj_cdata.c index ae66b4b5..11c84d8e 100644 --- a/src/lj_cdata.c +++ b/src/lj_cdata.c @@ -52,7 +52,18 @@ GCcdata *lj_cdata_newv(CTState *cts, CTypeID id, CTSize sz, CTSize align) /* Free a C data object. */ void LJ_FASTCALL lj_cdata_free(global_State *g, GCcdata *cd) { - if (LJ_LIKELY(!cdataisv(cd))) { + if (LJ_UNLIKELY(cd->marked & LJ_GC_CDATA_FIN)) { + GCobj *root; + cd->marked = curwhite(g) | LJ_GC_FINALIZED; + if ((root = gcref(g->gc.mmudata)) != NULL) { + setgcrefr(cd->nextgc, root->gch.nextgc); + setgcref(root->gch.nextgc, obj2gco(cd)); + setgcref(g->gc.mmudata, obj2gco(cd)); + } else { + setgcref(cd->nextgc, obj2gco(cd)); + setgcref(g->gc.mmudata, obj2gco(cd)); + } + } else if (LJ_LIKELY(!cdataisv(cd))) { CType *ct = ctype_raw(ctype_ctsG(g), cd->typeid); CTSize sz = ctype_hassize(ct->info) ? ct->size : CTSIZE_PTR; lua_assert(ctype_hassize(ct->info) || ctype_isfunc(ct->info) || diff --git a/src/lj_ctype.c b/src/lj_ctype.c index 956f0a09..ae360b54 100644 --- a/src/lj_ctype.c +++ b/src/lj_ctype.c @@ -540,7 +540,7 @@ GCstr *lj_ctype_repr_complex(lua_State *L, void *sp, CTSize size) /* -- C type state -------------------------------------------------------- */ /* Initialize C type table and state. */ -void lj_ctype_init(lua_State *L) +CTState *lj_ctype_init(lua_State *L) { CTState *cts = lj_mem_newt(L, sizeof(CTState), CTState); CType *ct = lj_mem_newvec(L, CTTYPETAB_MIN, CType); @@ -568,6 +568,7 @@ void lj_ctype_init(lua_State *L) } } setmref(G(L)->ctype_state, cts); + return cts; } /* Free C type table and state. */ diff --git a/src/lj_ctype.h b/src/lj_ctype.h index 555393db..a45767c2 100644 --- a/src/lj_ctype.h +++ b/src/lj_ctype.h @@ -158,6 +158,7 @@ typedef struct CTState { MSize sizetab; /* Size of C type table. */ lua_State *L; /* Lua state (needed for errors and allocations). */ global_State *g; /* Global state. */ + GCtab *finalizer; /* Map of cdata to finalizer. */ CTypeID1 hash[CTHASH_SIZE]; /* Hash anchors for C type table. */ } CTState; @@ -428,7 +429,7 @@ LJ_FUNC CTInfo lj_ctype_info(CTState *cts, CTypeID id, CTSize *szp); LJ_FUNC GCstr *lj_ctype_repr(lua_State *L, CTypeID id, GCstr *name); LJ_FUNC GCstr *lj_ctype_repr_int64(lua_State *L, uint64_t n, int isunsigned); LJ_FUNC GCstr *lj_ctype_repr_complex(lua_State *L, void *sp, CTSize size); -LJ_FUNC void lj_ctype_init(lua_State *L); +LJ_FUNC CTState *lj_ctype_init(lua_State *L); LJ_FUNC void lj_ctype_freestate(global_State *g); #endif diff --git a/src/lj_gc.c b/src/lj_gc.c index 793d8dac..f231b1ce 100644 --- a/src/lj_gc.c +++ b/src/lj_gc.c @@ -20,6 +20,7 @@ #include "lj_state.h" #include "lj_frame.h" #if LJ_HASFFI +#include "lj_ctype.h" #include "lj_cdata.h" #endif #include "lj_trace.h" @@ -170,8 +171,9 @@ static int gc_traverse_tab(global_State *g, GCtab *t) while ((c = *modestr++)) { if (c == 'k') weak |= LJ_GC_WEAKKEY; else if (c == 'v') weak |= LJ_GC_WEAKVAL; + else if (c == 'K') weak = (int)(~0u & ~LJ_GC_WEAKVAL); } - if (weak) { /* Weak tables are cleared in the atomic phase. */ + if (weak > 0) { /* Weak tables are cleared in the atomic phase. */ t->marked = cast_byte((t->marked & ~LJ_GC_WEAK) | weak); setgcrefr(t->gclist, g->gc.weak); setgcref(g->gc.weak, obj2gco(t)); @@ -458,53 +460,98 @@ static void gc_clearweak(GCobj *o) } } -/* Finalize one userdata object from mmudata list. */ +/* Call a userdata or cdata finalizer. */ +static void gc_call_finalizer(global_State *g, lua_State *L, + cTValue *mo, GCobj *o) +{ + /* Save and restore lots of state around the __gc callback. */ + uint8_t oldh = hook_save(g); + MSize oldt = g->gc.threshold; + int errcode; + TValue *top; + lj_trace_abort(g); + top = L->top; + L->top = top+2; + hook_entergc(g); /* Disable hooks and new traces during __gc. */ + g->gc.threshold = LJ_MAX_MEM; /* Prevent GC steps. */ + copyTV(L, top, mo); + setgcV(L, top+1, o, ~o->gch.gct); + errcode = lj_vm_pcall(L, top+1, 1+0, -1); /* Stack: |mo|o| -> | */ + hook_restore(g, oldh); + g->gc.threshold = oldt; /* Restore GC threshold. */ + if (errcode) + lj_err_throw(L, errcode); /* Propagate errors. */ +} + +/* Finalize one userdata or cdata object from the mmudata list. */ static void gc_finalize(lua_State *L) { global_State *g = G(L); GCobj *o = gcnext(gcref(g->gc.mmudata)); - GCudata *ud = gco2ud(o); cTValue *mo; lua_assert(gcref(g->jit_L) == NULL); /* Must not be called on trace. */ /* Unchain from list of userdata to be finalized. */ if (o == gcref(g->gc.mmudata)) setgcrefnull(g->gc.mmudata); else - setgcrefr(gcref(g->gc.mmudata)->gch.nextgc, ud->nextgc); - /* Add it back to the main userdata list and make it white. */ - setgcrefr(ud->nextgc, mainthread(g)->nextgc); - setgcref(mainthread(g)->nextgc, o); + setgcrefr(gcref(g->gc.mmudata)->gch.nextgc, o->gch.nextgc); makewhite(g, o); - /* Resolve the __gc metamethod. */ - mo = lj_meta_fastg(g, tabref(ud->metatable), MM_gc); - if (mo) { - /* Save and restore lots of state around the __gc callback. */ - uint8_t oldh = hook_save(g); - MSize oldt = g->gc.threshold; - int errcode; - TValue *top; - lj_trace_abort(g); - top = L->top; - L->top = top+2; - hook_entergc(g); /* Disable hooks and new traces during __gc. */ - g->gc.threshold = LJ_MAX_MEM; /* Prevent GC steps. */ - copyTV(L, top, mo); - setudataV(L, top+1, ud); - errcode = lj_vm_pcall(L, top+1, 1+0, -1); /* Stack: |mo|ud| -> | */ - hook_restore(g, oldh); - g->gc.threshold = oldt; /* Restore GC threshold. */ - if (errcode) - lj_err_throw(L, errcode); /* Propagate errors. */ +#if LJ_HASFFI + if (o->gch.gct == ~LJ_TCDATA) { + TValue tmp, *tv; + setgcrefr(o->gch.nextgc, g->gc.root); /* Add cdata back to the gc list. */ + setgcref(g->gc.root, o); + /* Resolve finalizer. */ + setcdataV(L, &tmp, gco2cd(o)); + tv = lj_tab_set(L, ctype_ctsG(g)->finalizer, &tmp); + if (!tvisnil(tv)) { + copyTV(L, &tmp, tv); + setnilV(tv); /* Clear entry in finalizer table. */ + gc_call_finalizer(g, L, &tmp, o); + } + return; } +#endif + /* Add userdata back to the main userdata list. */ + setgcrefr(o->gch.nextgc, mainthread(g)->nextgc); + setgcref(mainthread(g)->nextgc, o); + /* Resolve the __gc metamethod. */ + mo = lj_meta_fastg(g, tabref(gco2ud(o)->metatable), MM_gc); + if (mo) + gc_call_finalizer(g, L, mo, o); } /* Finalize all userdata objects from mmudata list. */ -void lj_gc_finalizeudata(lua_State *L) +void lj_gc_finalize_udata(lua_State *L) { while (gcref(G(L)->gc.mmudata) != NULL) gc_finalize(L); } +#if LJ_HASFFI +/* Finalize all cdata objects from finalizer table. */ +void lj_gc_finalize_cdata(lua_State *L) +{ + global_State *g = G(L); + CTState *cts = ctype_ctsG(g); + if (cts) { + GCtab *t = cts->finalizer; + Node *node = noderef(t->node); + ptrdiff_t i; + setgcrefnull(t->metatable); /* Mark finalizer table as disabled. */ + for (i = (ptrdiff_t)t->hmask; i >= 0; i--) + if (!tvisnil(&node[i].val) && tviscdata(&node[i].key)) { + GCobj *o = gcV(&node[i].key); + TValue tmp; + o->gch.marked &= ~LJ_GC_CDATA_FIN; + copyTV(L, &tmp, &node[i].val); + setnilV(&node[i].val); + gc_call_finalizer(g, L, &tmp, o); + } + } +} +#endif + /* Free all remaining GC objects. */ void lj_gc_freeall(global_State *g) { diff --git a/src/lj_gc.h b/src/lj_gc.h index 75b38db8..e3973d8e 100644 --- a/src/lj_gc.h +++ b/src/lj_gc.h @@ -20,6 +20,7 @@ enum { #define LJ_GC_FINALIZED 0x08 #define LJ_GC_WEAKKEY 0x08 #define LJ_GC_WEAKVAL 0x10 +#define LJ_GC_CDATA_FIN 0x10 #define LJ_GC_FIXED 0x20 #define LJ_GC_SFIXED 0x40 @@ -42,7 +43,12 @@ enum { /* Collector. */ LJ_FUNC size_t lj_gc_separateudata(global_State *g, int all); -LJ_FUNC void lj_gc_finalizeudata(lua_State *L); +LJ_FUNC void lj_gc_finalize_udata(lua_State *L); +#if LJ_HASFFI +LJ_FUNC void lj_gc_finalize_cdata(lua_State *L); +#else +#define lj_gc_finalize_cdata(L) UNUSED(L) +#endif LJ_FUNC void lj_gc_freeall(global_State *g); LJ_FUNCA int LJ_FASTCALL lj_gc_step(lua_State *L); LJ_FUNCA void LJ_FASTCALL lj_gc_step_fixtop(lua_State *L); diff --git a/src/lj_state.c b/src/lj_state.c index 85348aa7..11b820b2 100644 --- a/src/lj_state.c +++ b/src/lj_state.c @@ -225,7 +225,8 @@ static TValue *cpfinalize(lua_State *L, lua_CFunction dummy, void *ud) { UNUSED(dummy); UNUSED(ud); - lj_gc_finalizeudata(L); + lj_gc_finalize_udata(L); + lj_gc_finalize_cdata(L); /* Frame pop omitted. */ return NULL; }