diff --git a/doc/ext_ffi_api.html b/doc/ext_ffi_api.html index 5bd4b80c..2b6d1d86 100644 --- a/doc/ext_ffi_api.html +++ b/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.
++Creates a ctype object for the given ct and associates it with +a metatable. Only struct/union types, complex numbers +and vectors are allowed. Other types may be wrapped in a +struct, if needed. +
++The association with a metatable is permanent and cannot be changed +afterwards. Neither the contents of the metatable nor the +contents of an __index 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). +
++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 +__gc metamethod only applies to struct/union +types and performs an implicit ffi.gc() +call during creation of an instance. +
+Associates a finalizer with a pointer or aggregate cdata object. The diff --git a/doc/ext_ffi_semantics.html b/doc/ext_ffi_semantics.html index f9a118a0..d9aa27c8 100644 --- a/doc/ext_ffi_semantics.html +++ b/doc/ext_ffi_semantics.html @@ -582,6 +582,10 @@ Reference types are dereferenced before performing each of the operations below — the operation is applied to the C type pointed to by the reference.
++The pre-defined operations are always tried first before deferring to a +metamethod for a ctype (if defined). +
Memory areas returned by C functions (e.g. from malloc()) -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 +ffi.gc())). Pointers to +cdata objects are indistinguishable from pointers returned by C +functions (which is one of the reasons why the GC cannot follow them).
+The following code explains how to define metamethods for a C type. +We define a simple point type and add some operations to it: +
++ +① + + + +② + +③ + +④ + + + +⑤ + +⑥local ffi = require("ffi") +ffi.cdef[[ +typedef struct { double x, y; } point_t; +]] + +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 ++
+Here's the step-by-step explanation: +
++① This defines the C type for a +two-dimensional point object. +
++② We have to declare the variable +holding the point constructor first, because it's used inside of a +metamethod. +
++③ Let's define an __add +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 __len metamethod returns the distance of a point to +the origin. +
++④ If we run out of operators, we can +define named methods, too. Here the __index table defines an +area function. For custom indexing needs, one might want to +define __index and __newindex functions instead. +
++⑤ This associates the metamethods with +our C type. This only needs to be done once. For convenience, a +constructor is returned by +ffi.metatype(). +We're not required to use it, though. The original C 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. +
++Please note that the association with a metatable is permanent and +the metatable must not be modified afterwards! Ditto for the +__index table. +
++⑥ Here are some simple usage examples +for the point type and their expected results. The pre-defined +operations (such as a.x) can be freely mixed with the newly +defined metamethods. Note that area is a method and must be +called with the Lua syntax for methods: a:area(), not +a.area(). +
+Here's a list of common C idioms and their translation to the diff --git a/src/Makefile.dep b/src/Makefile.dep index 57904877..c06060ce 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_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 \ diff --git a/src/lib_ffi.c b/src/lib_ffi.c index cb001ae9..fe84ca7d 100644 --- a/src/lib_ffi.c +++ b/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. */ diff --git a/src/lj_asm.c b/src/lj_asm.c index a69f4461..8e7c98ee 100644 --- a/src/lj_asm.c +++ b/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); diff --git a/src/lj_carith.c b/src/lj_carith.c index a59665d8..8f644d83 100644 --- a/src/lj_carith.c +++ b/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,6 +189,31 @@ static int carith_int64(lua_State *L, CTState *cts, CDArith *ca, MMS mm) return 0; } +/* Handle ctype arithmetic metamethods. */ +static int lj_carith_meta(lua_State *L, CTState *cts, CDArith *ca, MMS mm) +{ + 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)); + else + repr[i] = typename(&L->base[i]); + } + lj_err_callerv(L, mm == MM_len ? LJ_ERR_FFI_BADLEN : + mm == MM_concat ? LJ_ERR_FFI_BADCONCAT : + mm < MM_add ? LJ_ERR_FFI_BADCOMP : LJ_ERR_FFI_BADARITH, + repr[0], repr[1]); + } + return lj_meta_tailcall(L, tv); +} + /* Arithmetic operators for cdata. */ int lj_carith_op(lua_State *L, MMS mm) { @@ -198,22 +225,7 @@ int lj_carith_op(lua_State *L, MMS mm) return 1; } } - /* NYI: per-cdata metamethods. */ - { - 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)); - else - repr[i] = typename(&L->base[i]); - } - lj_err_callerv(L, mm == MM_len ? LJ_ERR_FFI_BADLEN : - mm == MM_concat ? LJ_ERR_FFI_BADCONCAT : - mm < MM_add ? LJ_ERR_FFI_BADCOMP : LJ_ERR_FFI_BADARITH, - repr[0], repr[1]); - } - return 0; /* unreachable */ + return lj_carith_meta(L, cts, &ca, mm); } /* -- 64 bit integer arithmetic helpers ----------------------------------- */ diff --git a/src/lj_cdata.c b/src/lj_cdata.c index 11c84d8e..53605e7e 100644 --- a/src/lj_cdata.c +++ b/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)); + } + 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; } } - { - GCstr *s = lj_ctype_repr(cts->L, ctype_typeid(cts, ct), NULL); - lj_err_callerv(cts->L, LJ_ERR_FFI_BADIDX, strdata(s)); - } - return NULL; /* unreachable */ + *qual |= 1; /* Lookup failed. */ + return ct; /* But return the resolved raw type. */ } /* -- C data getters ------------------------------------------------------ */ diff --git a/src/lj_crecord.c b/src/lj_crecord.c index 0c4f5ca4..04c962d5 100644 --- a/src/lj_crecord.c +++ b/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; } } diff --git a/src/lj_ctype.c b/src/lj_ctype.c index ae360b54..0b59b48d 100644 --- a/src/lj_ctype.c +++ b/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. */ diff --git a/src/lj_ctype.h b/src/lj_ctype.h index a45767c2..f7a7121b 100644 --- a/src/lj_ctype.h +++ b/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);