Răsfoiți Sursa

FFI: Add ctype metamethods and ffi.metatype().

Mike Pall 14 ani în urmă
părinte
comite
3b6f37dd2c
11 a modificat fișierele cu 425 adăugiri și 82 ștergeri
  1. 25 0
      doc/ext_ffi_api.html
  2. 11 4
      doc/ext_ffi_semantics.html
  3. 93 0
      doc/ext_ffi_tutorial.html
  4. 7 6
      src/Makefile.dep
  5. 108 8
      src/lib_ffi.c
  6. 5 1
      src/lj_asm.c
  7. 27 15
      src/lj_carith.c
  8. 10 16
      src/lj_cdata.c
  9. 121 32
      src/lj_crecord.c
  10. 16 0
      src/lj_ctype.c
  11. 2 0
      src/lj_ctype.h

+ 25 - 0
doc/ext_ffi_api.html

@@ -238,6 +238,31 @@ This functions is mainly useful to override the pointer compatibility
 checks or to convert pointers to addresses or vice versa.
 </p>
 
+<h3 id="ffi_metatype"><tt>ctype = ffi.metatype(ct, metatable)</tt></h3>
+<p>
+Creates a ctype object for the given <tt>ct</tt> and associates it with
+a metatable. Only <tt>struct</tt>/<tt>union</tt> types, complex numbers
+and vectors are allowed. Other types may be wrapped in a
+<tt>struct</tt>, if needed.
+</p>
+<p>
+The association with a metatable is permanent and cannot be changed
+afterwards. Neither the contents of the <tt>metatable</tt> nor the
+contents of an <tt>__index</tt> table (if any) may be modified
+afterwards. The associated metatable automatically applies to all uses
+of this type, no matter how the objects are created or where they
+originate from. Note that pre-defined operations on types have
+precedence (e.g. declared field names cannot be overriden).
+</p>
+<p>
+All standard Lua metamethods are implemented. These are called directly,
+without shortcuts and on any mix of types. For binary operations, the
+left operand is checked first for a valid ctype metamethod. The
+<tt>__gc</tt> metamethod only applies to <tt>struct</tt>/<tt>union</tt>
+types and performs an implicit <a href="#ffi_gc"><tt>ffi.gc()</tt></a>
+call during creation of an instance.
+</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

+ 11 - 4
doc/ext_ffi_semantics.html

@@ -582,6 +582,10 @@ Reference types are dereferenced <em>before</em> performing each of
 the operations below &mdash; the operation is applied to the
 C&nbsp;type pointed to by the reference.
 </p>
+<p>
+The pre-defined operations are always tried first before deferring to a
+metamethod for a ctype (if defined).
+</p>
 
 <h3 id="cdata_array">Indexing a cdata object</h3>
 <ul>
@@ -803,9 +807,10 @@ vararg functions</a>.
 </p>
 <p>
 Memory areas returned by C functions (e.g. from <tt>malloc()</tt>)
-must be manually managed, of course. Pointers to cdata objects are
-indistinguishable from pointers returned by C functions (which is one
-of the reasons why the GC cannot follow them).
+must be manually managed, of course (or use
+<a href="ext_ffi_api.html#ffi_gc"><tt>ffi.gc()</tt></a>)). Pointers to
+cdata objects are indistinguishable from pointers returned by C
+functions (which is one of the reasons why the GC cannot follow them).
 </p>
 
 <h2 id="clib">C Library Namespaces</h2>
@@ -977,6 +982,9 @@ two.</li>
 value.</li>
 <li>Calls to C&nbsp;functions with 64 bit arguments or return values
 on 32 bit CPUs.</li>
+<li>Calls to ctype metamethods which are not plain functions.</li>
+<li>ctype <tt>__newindex</tt> tables and non-string lookups in ctype
+<tt>__index</tt> tables.</li>
 <li>Accesses to external variables in C&nbsp;library namespaces.</li>
 <li><tt>tostring()</tt> for cdata types.</li>
 <li>The following <a href="ext_ffi_api.html">ffi.* API</a> functions:
@@ -988,7 +996,6 @@ Other missing features:
 <ul>
 <li>Bit operations for 64&nbsp;bit types.</li>
 <li>Arithmetic for <tt>complex</tt> numbers.</li>
-<li>User-defined metamethods for C&nbsp;types.</li>
 <li>Callbacks from C&nbsp;code to Lua functions.</li>
 <li>Atomic handling of <tt>errno</tt>.</li>
 <li>Passing structs by value to vararg C&nbsp;functions.</li>

+ 93 - 0
doc/ext_ffi_tutorial.html

@@ -386,6 +386,99 @@ application might work on some systems, but would fail in a POSIX/x64
 environment.
 </p>
 
+<h2 id="metatype">Defining Metamethods for a C&nbsp;Type</h2>
+<p>
+The following code explains how to define metamethods for a C type.
+We define a simple point type and add some operations to it:
+</p>
+<pre class="code mark">
+<span class="codemark">&nbsp;
+&#9312;
+
+
+
+&#9313;
+
+&#9314;
+
+&#9315;
+
+
+
+&#9316;
+
+&#9317;</span>local ffi = require("ffi")
+ffi.cdef[[
+<span style="color:#00a000;">typedef struct { double x, y; } point_t;</span>
+]]
+
+local point
+local mt = {
+  __add = function(a, b) return point(a.x+b.x, a.y+b.y) end,
+  __len = function(a) return math.sqrt(a.x*a.x + a.y*a.y) end,
+  __index = {
+    area = function(a) return a.x*a.x + a.y*a.y end,
+  },
+}
+point = ffi.metatype("point_t", mt)
+
+local a = point(3, 4)
+print(a.x, a.y)  --> 3  4
+print(#a)        --> 5
+print(a:area())  --> 25
+local b = a + point(0.5, 8)
+print(#b)        --> 12.5
+</pre>
+<p>
+Here's the step-by-step explanation:
+</p>
+<p>
+<span class="mark">&#9312;</span> This defines the C&nbsp;type for a
+two-dimensional point object.
+</p>
+<p>
+<span class="mark">&#9313;</span> We have to declare the variable
+holding the point constructor first, because it's used inside of a
+metamethod.
+</p>
+<p>
+<span class="mark">&#9314;</span> Let's define an <tt>__add</tt>
+metamethod which adds the coordinates of two points and creates a new
+point object. For simplicity, this function assumes that both arguments
+are points. But it could be any mix of objects, if at least one operand
+is of the required type (e.g. adding a point plus a number or vice
+versa). Our <tt>__len</tt> metamethod returns the distance of a point to
+the origin.
+</p>
+<p>
+<span class="mark">&#9315;</span> If we run out of operators, we can
+define named methods, too. Here the <tt>__index</tt> table defines an
+<tt>area</tt> function. For custom indexing needs, one might want to
+define <tt>__index</tt> and <tt>__newindex</tt> functions instead.
+</p>
+<p>
+<span class="mark">&#9316;</span> This associates the metamethods with
+our C&nbsp;type. This only needs to be done once. For convenience, a
+constructor is returned by
+<a href="ffi_ext_api.html#ffi_metatype"><tt>ffi.metatype()</tt></a>.
+We're not required to use it, though. The original C&nbsp;type can still
+be used e.g. to create an array of points. The metamethods automatically
+apply to any and all uses of this type.
+</p>
+<p>
+Please note that the association with a metatable is permanent and
+<b>the metatable must not be modified afterwards!</b> Ditto for the
+<tt>__index</tt> table.
+</p>
+<p>
+<span class="mark">&#9317;</span> Here are some simple usage examples
+for the point type and their expected results. The pre-defined
+operations (such as <tt>a.x</tt>) can be freely mixed with the newly
+defined metamethods. Note that <tt>area</tt> is a method and must be
+called with the Lua syntax for methods: <tt>a:area()</tt>, not
+<tt>a.area()</tt>.
+</p>
+
 <h2 id="idioms">Translating C&nbsp;Idioms</h2>
 <p>
 Here's a list of common C&nbsp;idioms and their translation to the

+ 7 - 6
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_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
+ lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_meta.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 \
@@ -57,8 +57,8 @@ lj_asm.o: lj_asm.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \
 lj_bc.o: lj_bc.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_bc.h \
  lj_bcdef.h
 lj_carith.o: lj_carith.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
- lj_gc.h lj_err.h lj_errmsg.h lj_ctype.h lj_cconv.h lj_cdata.h \
- lj_carith.h
+ lj_gc.h lj_err.h lj_errmsg.h lj_tab.h lj_meta.h lj_ctype.h lj_cconv.h \
+ lj_cdata.h lj_carith.h
 lj_ccall.o: lj_ccall.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_ctype.h lj_cconv.h lj_cdata.h \
  lj_ccall.h lj_trace.h lj_jit.h lj_ir.h lj_dispatch.h lj_bc.h \
@@ -77,7 +77,8 @@ lj_cparse.o: lj_cparse.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
 lj_crecord.o: lj_crecord.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
  lj_err.h lj_errmsg.h lj_str.h lj_tab.h lj_frame.h lj_bc.h lj_ctype.h \
  lj_gc.h lj_cparse.h lj_cconv.h lj_clib.h lj_ir.h lj_jit.h lj_iropt.h \
- lj_trace.h lj_dispatch.h lj_traceerr.h lj_ffrecord.h lj_crecord.h
+ lj_trace.h lj_dispatch.h lj_traceerr.h lj_record.h lj_ffrecord.h \
+ lj_crecord.h
 lj_ctype.o: lj_ctype.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_ctype.h
 lj_dispatch.o: lj_dispatch.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \

+ 108 - 8
src/lib_ffi.c

@@ -18,6 +18,7 @@
 #include "lj_err.h"
 #include "lj_str.h"
 #include "lj_tab.h"
+#include "lj_meta.h"
 #include "lj_ctype.h"
 #include "lj_cparse.h"
 #include "lj_cdata.h"
@@ -96,6 +97,41 @@ static int32_t ffi_checkint(lua_State *L, int narg)
 
 #define LJLIB_MODULE_ffi_meta
 
+/* Handle ctype __index/__newindex metamethods. */
+static int ffi_index_meta(lua_State *L, CTState *cts, CType *ct, MMS mm)
+{
+  CTypeID id = ctype_typeid(cts, ct);
+  cTValue *tv = lj_ctype_meta(cts, id, mm);
+  TValue *base = L->base;
+  if (!tv) {
+    const char *s;
+  err_index:
+    s = strdata(lj_ctype_repr(L, id, NULL));
+    if (tvisstr(L->base+1))
+      lj_err_callerv(L, LJ_ERR_FFI_BADMEMBER, s, strVdata(L->base+1));
+    else
+      lj_err_callerv(L, LJ_ERR_FFI_BADIDX, s);
+  }
+  if (!tvisfunc(tv)) {
+    if (mm == MM_index) {
+      cTValue *o = lj_meta_tget(L, tv, base+1);
+      if (o) {
+	if (tvisnil(o)) goto err_index;
+	copyTV(L, L->top-1, o);
+	return 1;
+      }
+    } else {
+      TValue *o = lj_meta_tset(L, tv, base+1);
+      if (o) {
+	copyTV(L, o, base+2);
+	return 0;
+      }
+    }
+    tv = L->top-1;
+  }
+  return lj_meta_tailcall(L, tv);
+}
+
 LJLIB_CF(ffi_meta___index)	LJLIB_REC(cdata_index 0)
 {
   CTState *cts = ctype_cts(L);
@@ -106,6 +142,8 @@ LJLIB_CF(ffi_meta___index)	LJLIB_REC(cdata_index 0)
   if (!(o+1 < L->top && tviscdata(o)))  /* Also checks for presence of key. */
     lj_err_argt(L, 1, LUA_TCDATA);
   ct = lj_cdata_index(cts, cdataV(o), o+1, &p, &qual);
+  if ((qual & 1))
+    return ffi_index_meta(L, cts, ct, MM_index);
   if (lj_cdata_get(cts, ct, L->top-1, p))
     lj_gc_check(L);
   return 1;
@@ -121,6 +159,11 @@ LJLIB_CF(ffi_meta___newindex)	LJLIB_REC(cdata_index 1)
   if (!(o+2 < L->top && tviscdata(o)))  /* Also checks for key and value. */
     lj_err_argt(L, 1, LUA_TCDATA);
   ct = lj_cdata_index(cts, cdataV(o), o+1, &p, &qual);
+  if ((qual & 1)) {
+    if ((qual & CTF_CONST))
+      lj_err_caller(L, LJ_ERR_FFI_WRCONST);
+    return ffi_index_meta(L, cts, ct, MM_newindex);
+  }
   lj_cdata_set(cts, ct, p, o+2, qual);
   return 0;
 }
@@ -138,7 +181,7 @@ LJLIB_CF(ffi_meta___eq)		LJLIB_REC(cdata_arith MM_eq)
   return ffi_arith(L);
 }
 
-LJLIB_CF(ffi_meta___len)
+LJLIB_CF(ffi_meta___len)	LJLIB_REC(cdata_arith MM_len)
 {
   return ffi_arith(L);
 }
@@ -153,11 +196,21 @@ LJLIB_CF(ffi_meta___le)		LJLIB_REC(cdata_arith MM_le)
   return ffi_arith(L);
 }
 
-LJLIB_CF(ffi_meta___concat)
+LJLIB_CF(ffi_meta___concat)	LJLIB_REC(cdata_arith MM_concat)
 {
   return ffi_arith(L);
 }
 
+/* Handle ctype __call metamethod. */
+static int ffi_call_meta(lua_State *L, CTypeID id)
+{
+  CTState *cts = ctype_cts(L);
+  cTValue *tv = lj_ctype_meta(cts, id, MM_call);
+  if (!tv)
+    lj_err_callerv(L, LJ_ERR_FFI_BADCALL, strdata(lj_ctype_repr(L, id, NULL)));
+  return lj_meta_tailcall(L, tv);
+}
+
 /* Forward declaration. */
 static int lj_cf_ffi_new(lua_State *L);
 
@@ -168,8 +221,7 @@ LJLIB_CF(ffi_meta___call)	LJLIB_REC(cdata_call)
   if (cd->typeid == CTID_CTYPEID)
     return lj_cf_ffi_new(L);
   if ((ret = lj_ccall_func(L, cd)) < 0)
-    lj_err_callerv(L, LJ_ERR_FFI_BADCALL,
-		   strdata(lj_ctype_repr(L, cd->typeid, NULL)));
+    return ffi_call_meta(L, cd->typeid);
   return ret;
 }
 
@@ -226,6 +278,12 @@ LJLIB_CF(ffi_meta___tostring)
       setstrV(L, L->top-1, lj_ctype_repr_int64(L, *(uint64_t *)cdataptr(cd),
 					       (ct->info & CTF_UNSIGNED)));
       goto checkgc;
+    } else if (ctype_isstruct(ct->info) || ctype_isvector(ct->info)) {
+      /* Handle ctype __tostring metamethod. */
+      CTState *cts = ctype_cts(L);
+      cTValue *tv = lj_ctype_meta(cts, id, MM_tostring);
+      if (tv)
+	return lj_meta_tailcall(L, tv);
     }
   }
   lj_str_pushf(L, msg, strdata(lj_ctype_repr(L, id, NULL)), cdataptr(cd));
@@ -234,6 +292,8 @@ checkgc:
   return 1;
 }
 
+LJLIB_PUSH("ffi") LJLIB_SET(__metatable)
+
 #include "lj_libdef.h"
 
 /* -- C library metamethods ----------------------------------------------- */
@@ -331,14 +391,14 @@ LJLIB_CF(ffi_new)	LJLIB_REC(.)
 {
   CTState *cts = ctype_cts(L);
   CTypeID id = ffi_checkctype(L, cts);
+  CType *ct = ctype_raw(cts, id);
   CTSize sz;
   CTInfo info = lj_ctype_info(cts, id, &sz);
   TValue *o = L->base+1;
   GCcdata *cd;
   if ((info & CTF_VLA)) {
     o++;
-    sz = lj_ctype_vlsize(cts, ctype_raw(cts, id),
-			 (CTSize)ffi_checkint(L, 2));
+    sz = lj_ctype_vlsize(cts, ct, (CTSize)ffi_checkint(L, 2));
   }
   if (sz == CTSIZE_INVALID)
     lj_err_arg(L, 1, LJ_ERR_FFI_INVSIZE);
@@ -347,8 +407,21 @@ LJLIB_CF(ffi_new)	LJLIB_REC(.)
   else
     cd = lj_cdata_newv(cts, id, sz, ctype_align(info));
   setcdataV(L, o-1, cd);  /* Anchor the uninitialized cdata. */
-  lj_cconv_ct_init(cts, ctype_raw(cts, id), sz, cdataptr(cd),
+  lj_cconv_ct_init(cts, ct, sz, cdataptr(cd),
 		   o, (MSize)(L->top - o));  /* Initialize cdata. */
+  if (ctype_isstruct(ct->info)) {
+    /* Handle ctype __gc metamethod. Use the fast lookup here. */
+    cTValue *tv = lj_tab_getint(cts->metatype, (int32_t)id);
+    if (tv && tvistab(tv) && (tv = lj_meta_fast(L, tabV(tv), MM_gc))) {
+      GCtab *t = cts->finalizer;
+      if (gcref(t->metatable)) {
+	/* Add to finalizer table, if still enabled. */
+	copyTV(L, lj_tab_set(L, t, o-1), tv);
+	lj_gc_anybarriert(L, t);
+	cd->marked |= LJ_GC_CDATA_FIN;
+      }
+    }
+  }
   L->top = o;  /* Only return the cdata itself. */
   lj_gc_check(L);
   return 1;
@@ -521,7 +594,33 @@ LJLIB_CF(ffi_abi)	LJLIB_REC(.)
 
 #undef H_
 
-LJLIB_PUSH(top-7) LJLIB_SET(!)  /* Store reference to weak table. */
+LJLIB_PUSH(top-8) LJLIB_SET(!)  /* Store reference to metatype table. */
+
+LJLIB_CF(ffi_metatype)
+{
+  CTState *cts = ctype_cts(L);
+  CTypeID id = ffi_checkctype(L, cts);
+  GCtab *mt = lj_lib_checktab(L, 2);
+  GCtab *t = cts->metatype;
+  CType *ct = ctype_get(cts, id);  /* Only allow raw types. */
+  TValue *tv;
+  GCcdata *cd;
+  if (!(ctype_isstruct(ct->info) || ctype_iscomplex(ct->info) ||
+	ctype_isvector(ct->info)))
+    lj_err_arg(L, 1, LJ_ERR_FFI_INVTYPE);
+  tv = lj_tab_setint(L, t, (int32_t)id);
+  if (!tvisnil(tv))
+    lj_err_caller(L, LJ_ERR_PROTMT);
+  settabV(L, tv, mt);
+  lj_gc_anybarriert(L, t);
+  cd = lj_cdata_new(cts, CTID_CTYPEID, 4);
+  *(CTypeID *)cdataptr(cd) = id;
+  setcdataV(L, L->top-1, cd);
+  lj_gc_check(L);
+  return 1;
+}
+
+LJLIB_PUSH(top-7) LJLIB_SET(!)  /* Store reference to finalizer table. */
 
 LJLIB_CF(ffi_gc)
 {
@@ -590,6 +689,7 @@ static void ffi_register_module(lua_State *L)
 LUALIB_API int luaopen_ffi(lua_State *L)
 {
   CTState *cts = lj_ctype_init(L);
+  settabV(L, L->top++, (cts->metatype = lj_tab_new(L, 0, 0)));
   cts->finalizer = ffi_finalizer(L);
   LJ_LIB_REG(L, NULL, ffi_meta);
   /* NOBARRIER: basemt is a GC root. */

+ 5 - 1
src/lj_asm.c

@@ -2558,6 +2558,7 @@ static void asm_cnew(ASMState *as, IRIns *ir)
 	      lj_ctype_size(cts, typeid) : (CTSize)IR(ir->op2)->i;
   const CCallInfo *ci = &lj_ir_callinfo[IRCALL_lj_mem_newgco];
   IRRef args[2];
+  int gcfin = 0;
   lua_assert(sz != CTSIZE_INVALID);
 
   args[0] = ASMREF_L;     /* lua_State *L */
@@ -2604,12 +2605,15 @@ static void asm_cnew(ASMState *as, IRIns *ir)
     } while (1);
 #endif
     lua_assert(sz == 4 || (sz == 8 && (LJ_64 || LJ_HASFFI)));
+  } else {
+    if (lj_ctype_meta(cts, typeid, MM_gc) != NULL)
+      gcfin = LJ_GC_CDATA_FIN;
   }
 
   /* Combine initialization of marked, gct and typeid. */
   emit_movtomro(as, RID_ECX, RID_RET, offsetof(GCcdata, marked));
   emit_gri(as, XG_ARITHi(XOg_OR), RID_ECX,
-	   (int32_t)((~LJ_TCDATA<<8)+(typeid<<16)));
+	   (int32_t)((~LJ_TCDATA<<8)+(typeid<<16)+gcfin));
   emit_gri(as, XG_ARITHi(XOg_AND), RID_ECX, LJ_GC_WHITES);
   emit_opgl(as, XO_MOVZXb, RID_ECX, gc.currentwhite);
 

+ 27 - 15
src/lj_carith.c

@@ -9,6 +9,8 @@
 
 #include "lj_gc.h"
 #include "lj_err.h"
+#include "lj_tab.h"
+#include "lj_meta.h"
 #include "lj_ctype.h"
 #include "lj_cconv.h"
 #include "lj_cdata.h"
@@ -187,24 +189,20 @@ static int carith_int64(lua_State *L, CTState *cts, CDArith *ca, MMS mm)
   return 0;
 }
 
-/* Arithmetic operators for cdata. */
-int lj_carith_op(lua_State *L, MMS mm)
+/* Handle ctype arithmetic metamethods. */
+static int lj_carith_meta(lua_State *L, CTState *cts, CDArith *ca, MMS mm)
 {
-  CTState *cts = ctype_cts(L);
-  CDArith ca;
-  if (carith_checkarg(L, cts, &ca)) {
-    if (carith_int64(L, cts, &ca, mm) || carith_ptr(L, cts, &ca, mm)) {
-      copyTV(L, &G(L)->tmptv2, L->top-1);  /* Remember for trace recorder. */
-      return 1;
-    }
-  }
-  /* NYI: per-cdata metamethods. */
-  {
+  cTValue *tv = NULL;
+  if (tviscdata(L->base))
+    tv = lj_ctype_meta(cts, cdataV(L->base)->typeid, mm);
+  if (!tv && L->base+1 < L->top && tviscdata(L->base+1))
+    tv = lj_ctype_meta(cts, cdataV(L->base+1)->typeid, mm);
+  if (!tv) {
     const char *repr[2];
     int i;
     for (i = 0; i < 2; i++) {
-      if (ca.ct[i])
-	repr[i] = strdata(lj_ctype_repr(L, ctype_typeid(cts, ca.ct[i]), NULL));
+      if (ca->ct[i])
+	repr[i] = strdata(lj_ctype_repr(L, ctype_typeid(cts, ca->ct[i]), NULL));
       else
 	repr[i] = typename(&L->base[i]);
     }
@@ -213,7 +211,21 @@ int lj_carith_op(lua_State *L, MMS mm)
 		      mm < MM_add ? LJ_ERR_FFI_BADCOMP : LJ_ERR_FFI_BADARITH,
 		   repr[0], repr[1]);
   }
-  return 0;  /* unreachable */
+  return lj_meta_tailcall(L, tv);
+}
+
+/* Arithmetic operators for cdata. */
+int lj_carith_op(lua_State *L, MMS mm)
+{
+  CTState *cts = ctype_cts(L);
+  CDArith ca;
+  if (carith_checkarg(L, cts, &ca)) {
+    if (carith_int64(L, cts, &ca, mm) || carith_ptr(L, cts, &ca, mm)) {
+      copyTV(L, &G(L)->tmptv2, L->top-1);  /* Remember for trace recorder. */
+      return 1;
+    }
+  }
+  return lj_carith_meta(L, cts, &ca, mm);
 }
 
 /* -- 64 bit integer arithmetic helpers ----------------------------------- */

+ 10 - 16
src/lj_cdata.c

@@ -129,13 +129,7 @@ collect_attrib:
     }
   } else if (tvisstr(key)) {  /* String key. */
     GCstr *name = strV(key);
-    if (ctype_isptr(ct->info)) {  /* Automatically perform '->'. */
-      if (ctype_isstruct(ctype_rawchild(cts, ct)->info)) {
-	p = (uint8_t *)cdata_getptr(p, ct->size);
-	ct = ctype_child(cts, ct);
-	goto collect_attrib;
-      }
-    } if (ctype_isstruct(ct->info)) {
+    if (ctype_isstruct(ct->info)) {
       CTSize ofs;
       CType *fct = lj_ctype_getfield(cts, ct, name, &ofs);
       if (fct) {
@@ -155,7 +149,7 @@ collect_attrib:
       }
     } else if (cd->typeid == CTID_CTYPEID) {
       /* Allow indexing a (pointer to) struct constructor to get constants. */
-      CType *sct = ct = ctype_raw(cts, *(CTypeID *)p);
+      CType *sct = ctype_raw(cts, *(CTypeID *)p);
       if (ctype_isptr(sct->info))
 	sct = ctype_rawchild(cts, sct);
       if (ctype_isstruct(sct->info)) {
@@ -165,16 +159,16 @@ collect_attrib:
 	  return fct;
       }
     }
-    {
-      GCstr *s = lj_ctype_repr(cts->L, ctype_typeid(cts, ct), NULL);
-      lj_err_callerv(cts->L, LJ_ERR_FFI_BADMEMBER, strdata(s), strdata(name));
-    }
   }
-  {
-    GCstr *s = lj_ctype_repr(cts->L, ctype_typeid(cts, ct), NULL);
-    lj_err_callerv(cts->L, LJ_ERR_FFI_BADIDX, strdata(s));
+  if (ctype_isptr(ct->info)) {  /* Automatically perform '->'. */
+    if (ctype_isstruct(ctype_rawchild(cts, ct)->info)) {
+      p = (uint8_t *)cdata_getptr(p, ct->size);
+      ct = ctype_child(cts, ct);
+      goto collect_attrib;
+    }
   }
-  return NULL;  /* unreachable */
+  *qual |= 1;  /* Lookup failed. */
+  return ct;  /* But return the resolved raw type. */
 }
 
 /* -- C data getters ------------------------------------------------------ */

+ 121 - 32
src/lj_crecord.c

@@ -22,6 +22,7 @@
 #include "lj_jit.h"
 #include "lj_iropt.h"
 #include "lj_trace.h"
+#include "lj_record.h"
 #include "lj_ffrecord.h"
 #include "lj_crecord.h"
 #include "lj_dispatch.h"
@@ -459,6 +460,41 @@ static TRef crec_reassoc_ofs(jit_State *J, TRef tr, ptrdiff_t *ofsp, MSize sz)
   return tr;
 }
 
+/* Record ctype __index/__newindex metamethods. */
+static void crec_index_meta(jit_State *J, CTState *cts, CType *ct,
+			    RecordFFData *rd)
+{
+  CTypeID id = ctype_typeid(cts, ct);
+  cTValue *tv = lj_ctype_meta(cts, id, rd->data ? MM_newindex : MM_index);
+  if (!tv)
+    lj_trace_err(J, LJ_TRERR_BADTYPE);
+  if (tvisfunc(tv)) {
+    J->base[-1] = lj_ir_kfunc(J, funcV(tv)) | TREF_FRAME;
+    rd->nres = -1;  /* Pending tailcall. */
+  } else if (rd->data == 0 && tvistab(tv) && tref_isstr(J->base[1])) {
+    /* Specialize to result of __index lookup. */
+    cTValue *o = lj_tab_get(J->L, tabV(tv), &rd->argv[1]);
+    IRType t = itype2irt(o);
+    if (tvisgcv(o))
+      J->base[0] = lj_ir_kgc(J, gcV(o), t);
+    else if (tvisint(o))
+      J->base[0] = lj_ir_kint(J, intV(o));
+    else if (tvisnum(o))
+      J->base[0] = lj_ir_knumint(J, numV(o));
+    else if (tvisbool(o))
+      J->base[0] = TREF_PRI(t);
+    else
+      lj_trace_err(J, LJ_TRERR_BADTYPE);
+    /* Always specialize to the key. */
+    emitir(IRTG(IR_EQ, IRT_STR), J->base[1], lj_ir_kstr(J, strV(&rd->argv[1])));
+  } else {
+    /* NYI: resolving of non-function metamethods. */
+    /* NYI: non-string keys for __index table. */
+    /* NYI: stores to __newindex table. */
+    lj_trace_err(J, LJ_TRERR_BADTYPE);
+  }
+}
+
 void LJ_FASTCALL recff_cdata_index(jit_State *J, RecordFFData *rd)
 {
   TRef idx, ptr = J->base[0];
@@ -477,12 +513,13 @@ void LJ_FASTCALL recff_cdata_index(jit_State *J, RecordFFData *rd)
     ptr = crec_reassoc_ofs(J, ptr, &ofs, 1);
   }
 
+again:
   idx = J->base[1];
   if (tref_isnumber(idx)) {
     idx = lj_opt_narrow_cindex(J, idx);
-  integer_key:
     if (ctype_ispointer(ct->info)) {
       CTSize sz;
+  integer_key:
       if ((ct->info & CTF_COMPLEX))
 	idx = emitir(IRT(IR_BAND, IRT_INTP), idx, lj_ir_kintp(J, 1));
       sz = lj_ctype_size(cts, (sid = ctype_cid(ct->info)));
@@ -495,7 +532,8 @@ void LJ_FASTCALL recff_cdata_index(jit_State *J, RecordFFData *rd)
     CType *ctk = ctype_raw(cts, cdk->typeid);
     IRType t;
     if (ctype_isenum(ctk->info)) ctk = ctype_child(cts, ctk);
-    if (ctype_isinteger(ctk->info) && (t = crec_ct2irt(ctk)) != IRT_CDATA) {
+    if (ctype_ispointer(ct->info) &&
+	ctype_isinteger(ctk->info) && (t = crec_ct2irt(ctk)) != IRT_CDATA) {
       if (ctk->size == 8) {
 	idx = emitir(IRT(IR_FLOAD, t), idx, IRFL_CDATA_INT64);
       } else {
@@ -513,22 +551,15 @@ void LJ_FASTCALL recff_cdata_index(jit_State *J, RecordFFData *rd)
     }
   } else if (tref_isstr(idx)) {
     GCstr *name = strV(&rd->argv[1]);
-    /* Always specialize to the field name. */
-    emitir(IRTG(IR_EQ, IRT_STR), idx, lj_ir_kstr(J, name));
     if (cd->typeid == CTID_CTYPEID)
       ct = ctype_raw(cts, crec_constructor(J, cd, ptr));
-    if (ctype_isptr(ct->info)) {  /* Automatically perform '->'. */
-      CType *cct = ctype_rawchild(cts, ct);
-      if (ctype_isstruct(cct->info)) {
-	ct = cct;
-	goto index_struct;
-      }
-    } else if (ctype_isstruct(ct->info)) {
+    if (ctype_isstruct(ct->info)) {
       CTSize fofs;
       CType *fct;
-index_struct:
       fct = lj_ctype_getfield(cts, ct, name, &fofs);
       if (fct) {
+	/* Always specialize to the field name. */
+	emitir(IRTG(IR_EQ, IRT_STR), idx, lj_ir_kstr(J, name));
 	if (ctype_isconstval(fct->info)) {
 	  if (fct->size >= 0x80000000u &&
 	      (ctype_child(cts, fct)->info & CTF_UNSIGNED)) {
@@ -546,11 +577,26 @@ index_struct:
 	ofs += (ptrdiff_t)fofs;
       }
     } else if (ctype_iscomplex(ct->info)) {
-      if (strdata(name)[0] == 'i') ofs += (ct->size >> 1);
-      sid = ctype_cid(ct->info);
+      if (name->len == 2 &&
+	  ((strdata(name)[0] == 'r' && strdata(name)[1] == 'e') ||
+	   (strdata(name)[0] == 'i' && strdata(name)[1] == 'm'))) {
+	/* Always specialize to the field name. */
+	emitir(IRTG(IR_EQ, IRT_STR), idx, lj_ir_kstr(J, name));
+	if (strdata(name)[0] == 'i') ofs += (ct->size >> 1);
+	sid = ctype_cid(ct->info);
+      }
     }
   }
-  if (!sid) lj_trace_err(J, LJ_TRERR_BADTYPE);
+  if (!sid) {
+    if (ctype_isptr(ct->info)) {  /* Automatically perform '->'. */
+      CType *cct = ctype_rawchild(cts, ct);
+      if (ctype_isstruct(cct->info)) {
+	ct = cct;
+	if (tref_isstr(idx)) goto again;
+      }
+    }
+    return crec_index_meta(J, cts, ct, rd);
+  }
 
   if (ofs)
     ptr = emitir(IRT(IR_ADD, IRT_PTR), ptr, lj_ir_kintp(J, ofs));
@@ -592,6 +638,7 @@ static void crec_alloc(jit_State *J, RecordFFData *rd, CTypeID id)
     J->base[0] = emitir(IRTG(IR_CNEWI, IRT_CDATA), trid, sp);
   } else {
     TRef trcd = emitir(IRTG(IR_CNEW, IRT_CDATA), trid, TREF_NIL);
+    cTValue *fin;
     J->base[0] = trcd;
     if (J->base[1] && !J->base[2] && !lj_cconv_multi_init(d, &rd->argv[1])) {
       goto single_init;
@@ -660,6 +707,24 @@ static void crec_alloc(jit_State *J, RecordFFData *rd, CTypeID id)
 	crec_ct_tv(J, d, dp, lj_ir_kint(J, 0), &tv);
       }
     }
+    /* Handle __gc metamethod. */
+    fin = lj_ctype_meta(cts, id, MM_gc);
+    if (fin) {
+      RecordIndex ix;
+      ix.idxchain = 0;
+      settabV(J->L, &ix.tabv, cts->finalizer);
+      ix.tab = lj_ir_ktab(J, cts->finalizer);
+      setboolV(&ix.keyv, 0);  /* The key is new. Dummy value is ok here. */
+      ix.key = trcd;
+      copyTV(J->L, &ix.valv, fin);
+      if (tvisfunc(fin))
+	ix.val = lj_ir_kfunc(J, funcV(fin));
+      else if (tviscdata(fin))
+	ix.val = lj_ir_kgc(J, obj2gco(cdataV(fin)), IRT_CDATA);
+      else
+	lj_trace_err(J, LJ_TRERR_BADTYPE);
+      lj_record_idx(J, &ix);
+    }
   }
 }
 
@@ -849,6 +914,27 @@ static TRef crec_arith_ptr(jit_State *J, TRef *sp, CType **s, MMS mm)
   }
 }
 
+/* Record ctype arithmetic metamethods. */
+static void crec_arith_meta(jit_State *J, CTState *cts, RecordFFData *rd)
+{
+  cTValue *tv = NULL;
+  if (J->base[0]) {
+    if (tviscdata(&rd->argv[0]))
+      tv = lj_ctype_meta(cts, argv2cdata(J, J->base[0], &rd->argv[0])->typeid,
+			 (MMS)rd->data);
+    if (!tv && J->base[1] && tviscdata(&rd->argv[1]))
+      tv = lj_ctype_meta(cts, argv2cdata(J, J->base[1], &rd->argv[1])->typeid,
+			 (MMS)rd->data);
+  }
+  if (tv && tvisfunc(tv)) {
+    J->base[-1] = lj_ir_kfunc(J, funcV(tv)) | TREF_FRAME;
+    rd->nres = -1;  /* Pending tailcall. */
+  } else {
+    /* NYI: non-function metamethods. */
+    lj_trace_err(J, LJ_TRERR_BADTYPE);
+  }
+}
+
 void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)
 {
   CTState *cts = ctype_ctsG(J2G(J));
@@ -858,7 +944,9 @@ void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)
   for (i = 0; i < 2; i++) {
     TRef tr = J->base[i];
     CType *ct = ctype_get(cts, CTID_DOUBLE);
-    if (tref_iscdata(tr)) {
+    if (!tr) {
+      goto trymeta;
+    } else if (tref_iscdata(tr)) {
       CTypeID id = argv2cdata(J, tr, &rd->argv[i])->typeid;
       ct = ctype_raw(cts, id);
       if (ctype_isptr(ct->info)) {  /* Resolve pointer or reference. */
@@ -876,11 +964,11 @@ void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)
       if (ctype_isenum(ct->info)) ct = ctype_child(cts, ct);
       if (ctype_isnum(ct->info)) {
 	IRType t = crec_ct2irt(ct);
-	if (t == IRT_CDATA) goto err_type;
+	if (t == IRT_CDATA) goto trymeta;
 	if (t == IRT_I64 || t == IRT_U64) lj_needsplit(J);
 	tr = emitir(IRT(IR_XLOAD, t), tr, 0);
       } else if (!(ctype_isptr(ct->info) || ctype_isrefarray(ct->info))) {
-	goto err_type;
+	goto trymeta;
       }
     } else if (tref_isnil(tr)) {
       tr = lj_ir_kptr(J, NULL);
@@ -888,7 +976,7 @@ void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)
     } else if (tref_isinteger(tr)) {
       ct = ctype_get(cts, CTID_INT32);
     } else if (!tref_isnum(tr)) {
-      goto err_type;
+      goto trymeta;
     }
   ok:
     s[i] = ct;
@@ -896,21 +984,22 @@ void LJ_FASTCALL recff_cdata_arith(jit_State *J, RecordFFData *rd)
   }
   {
     TRef tr;
-    if (!(tr = crec_arith_int64(J, sp, s, (MMS)rd->data)) &&
-	!(tr = crec_arith_ptr(J, sp, s, (MMS)rd->data))) {
-    err_type:
-      lj_trace_err(J, LJ_TRERR_BADTYPE);
-    }
-    /* Fixup cdata comparisons, too. Avoids some cdata escapes. */
-    if (J->postproc == LJ_POST_FIXGUARD && frame_iscont(J->L->base-1)) {
-      const BCIns *pc = frame_contpc(J->L->base-1) - 1;
-      if (bc_op(*pc) <= BC_ISNEP) {
-	setframe_pc(&J2G(J)->tmptv, pc);
-	J2G(J)->tmptv.u32.lo = ((tref_istrue(tr) ^ bc_op(*pc)) & 1);
-	J->postproc = LJ_POST_FIXCOMP;
+    if ((tr = crec_arith_int64(J, sp, s, (MMS)rd->data)) ||
+	(tr = crec_arith_ptr(J, sp, s, (MMS)rd->data))) {
+      J->base[0] = tr;
+      /* Fixup cdata comparisons, too. Avoids some cdata escapes. */
+      if (J->postproc == LJ_POST_FIXGUARD && frame_iscont(J->L->base-1)) {
+	const BCIns *pc = frame_contpc(J->L->base-1) - 1;
+	if (bc_op(*pc) <= BC_ISNEP) {
+	  setframe_pc(&J2G(J)->tmptv, pc);
+	  J2G(J)->tmptv.u32.lo = ((tref_istrue(tr) ^ bc_op(*pc)) & 1);
+	  J->postproc = LJ_POST_FIXCOMP;
+	}
       }
+    } else {
+    trymeta:
+      crec_arith_meta(J, cts, rd);
     }
-    J->base[0] = tr;
   }
 }
 

+ 16 - 0
src/lj_ctype.c

@@ -306,6 +306,22 @@ CTInfo lj_ctype_info(CTState *cts, CTypeID id, CTSize *szp)
   return qual;
 }
 
+/* Get ctype metamethod. */
+cTValue *lj_ctype_meta(CTState *cts, CTypeID id, MMS mm)
+{
+  CType *ct = ctype_get(cts, id);
+  cTValue *tv;
+  while (ctype_isattrib(ct->info)) {
+    id = ctype_cid(ct->info);
+    ct = ctype_get(cts, id);
+  }
+  tv = lj_tab_getint(cts->metatype, (int32_t)id);
+  if (tv && tvistab(tv) &&
+      (tv = lj_tab_getstr(tabV(tv), mmname_str(cts->g, mm))) && !tvisnil(tv))
+    return tv;
+  return NULL;
+}
+
 /* -- C type representation ----------------------------------------------- */
 
 /* Fixed max. length of a C type representation. */

+ 2 - 0
src/lj_ctype.h

@@ -159,6 +159,7 @@ typedef struct CTState {
   lua_State *L;		/* Lua state (needed for errors and allocations). */
   global_State *g;	/* Global state. */
   GCtab *finalizer;	/* Map of cdata to finalizer. */
+  GCtab *metatype;	/* Map of CTypeID to metatable. */
   CTypeID1 hash[CTHASH_SIZE];  /* Hash anchors for C type table. */
 } CTState;
 
@@ -426,6 +427,7 @@ LJ_FUNC CType *lj_ctype_rawref(CTState *cts, CTypeID id);
 LJ_FUNC CTSize lj_ctype_size(CTState *cts, CTypeID id);
 LJ_FUNC CTSize lj_ctype_vlsize(CTState *cts, CType *ct, CTSize nelem);
 LJ_FUNC CTInfo lj_ctype_info(CTState *cts, CTypeID id, CTSize *szp);
+LJ_FUNC cTValue *lj_ctype_meta(CTState *cts, CTypeID id, MMS mm);
 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);