Pārlūkot izejas kodu

FFI: Add ffi.gc() function for finalization of cdata objects.

Mike Pall 14 gadi atpakaļ
vecāks
revīzija
83a37aeca7
9 mainītis faili ar 191 papildinājumiem un 57 dzēšanām
  1. 28 0
      doc/ext_ffi_api.html
  2. 5 5
      src/Makefile.dep
  3. 58 19
      src/lib_ffi.c
  4. 12 1
      src/lj_cdata.c
  5. 2 1
      src/lj_ctype.c
  6. 2 1
      src/lj_ctype.h
  7. 75 28
      src/lj_gc.c
  8. 7 1
      src/lj_gc.h
  9. 2 1
      src/lj_state.c

+ 28 - 0
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.
 </p>
 
+<h3 id="ffi_gc"><tt>cdata = ffi.gc(cdata, finalizer)</tt></h3>
+<p>
+Associates a finalizer with a pointer or aggregate cdata object. The
+cdata object is returned unchanged.
+</p>
+<p>
+This function allows safe integration of unmanaged resources into the
+automatic memory management of the LuaJIT garbage collector. Typical
+usage:
+</p>
+<pre class="code">
+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)
+</pre>
+<p>
+A cdata finalizer works like the <tt>__gc</tt> 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 <tt>nil</tt>
+finalizer, e.g. right before explicitly deleting a resource:
+</p>
+<pre class="code">
+ffi.C.free(ffi.gc(p, nil)) -- Manually free the memory.
+</pre>
+
 <h2 id="info">C&nbsp;Type Information</h2>
 <p>
 The following API functions return information about C&nbsp;types.

+ 5 - 5
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 \

+ 58 - 19
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<<MM_mode));
+  return t;
+}
+
 LUALIB_API int luaopen_ffi(lua_State *L)
 {
-  lj_ctype_init(L);
+  CTState *cts = lj_ctype_init(L);
+  cts->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)));

+ 12 - 1
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) ||

+ 2 - 1
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. */

+ 2 - 1
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

+ 75 - 28
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)
 {

+ 7 - 1
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);

+ 2 - 1
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;
 }