diff --git a/cmake/BuildLibYAML.cmake b/cmake/BuildLibYAML.cmake index 50e66a35863cafc7274f16016e17ba5a7d1cdc52..01eb6116d74420ef98463cf494eb362fbb6a9e33 100644 --- a/cmake/BuildLibYAML.cmake +++ b/cmake/BuildLibYAML.cmake @@ -1,7 +1,7 @@ # # A macro to build the bundled liblua-yaml macro(libyaml_build) - set(yaml_src ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.c + set(yaml_src ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/api.c ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/dumper.c ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/emitter.c @@ -14,6 +14,10 @@ macro(libyaml_build) set_source_files_properties(${yaml_src} PROPERTIES COMPILE_FLAGS "-std=c99") + set_source_files_properties( + ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc + PROPERTIES COMPILE_FLAGS + "-std=gnu++0x -D__STDC_FORMAT_MACROS=1 -D__STDC_LIMIT_MACROS=1") add_library(yaml STATIC ${yaml_src}) diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt index 43f9dfecea96e4fddcb33a8438fd901743f2b222..0dac09813a5628e03946c0167fd40954ae6617c6 100644 --- a/src/box/CMakeLists.txt +++ b/src/box/CMakeLists.txt @@ -9,6 +9,7 @@ lua_source(lua_sources lua/schema.lua) lua_source(lua_sources lua/box.lua) lua_source(lua_sources lua/box_net.lua) lua_source(lua_sources lua/misc.lua) +lua_source(lua_sources lua/tuple.lua) add_custom_target(box_generate_lua_sources WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src/box diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index 9f68a0d3e78eabdae43d0eaee548bc8ff2d34b77..d112dd06f02338a249a25b06a233da73198ef6ab 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -168,9 +168,7 @@ port_add_lua_multret(struct port *port, struct lua_State *L) */ lua_pushnil(L); int has_keys = lua_next(L, 1); - if (has_keys && - (lua_istable(L, -1) || lua_isuserdata(L, -1))) { - + if (has_keys && (lua_istable(L, -1) || lua_istuple(L, -1))) { do { port_add_lua_ret(port, L, lua_gettop(L)); lua_pop(L, 1); diff --git a/src/box/lua/tuple.cc b/src/box/lua/tuple.cc index 5c71c113b21c94cded5f9511a5b39d29c00e9f27..d8522df9340539eb2230992622951fa31b0706ae 100644 --- a/src/box/lua/tuple.cc +++ b/src/box/lua/tuple.cc @@ -35,6 +35,9 @@ #include "lua/utils.h" #include "lua/msgpack.h" #include "third_party/lua-yaml/lyaml.h" +extern "C" { +#include <lj_obj.h> +} /** {{{ box.tuple Lua library * @@ -52,26 +55,39 @@ static const char *tuplelib_name = "box.tuple"; static const char *tuple_iteratorlib_name = "box.tuple.iterator"; static int tuple_totable_mt_ref = 0; /* a precreated metable for totable() */ +extern char tuple_lua[]; /* Lua source */ + +uint32_t CTID_CONST_STRUCT_TUPLE_REF; static inline struct tuple * lua_checktuple(struct lua_State *L, int narg) { - struct tuple *t = *(struct tuple **) luaL_checkudata(L, narg, tuplelib_name); - assert(t->refs); - return t; + struct tuple *tuple = lua_istuple(L, narg); + if (tuple == NULL) { + luaL_error(L, "Invalid argument #%d (box.tuple expected, got %s)", + narg, lua_typename(L, lua_type(L, narg))); + } + + return tuple; } struct tuple * lua_istuple(struct lua_State *L, int narg) { - if (lua_getmetatable(L, narg) == 0) + assert(CTID_CONST_STRUCT_TUPLE_REF != 0); + uint32_t ctypeid; + void *data; + + if (lua_type(L, narg) != LUA_TCDATA) return NULL; - luaL_getmetatable(L, tuplelib_name); - struct tuple *tuple = 0; - if (lua_equal(L, -1, -2)) - tuple = *(struct tuple **) lua_touserdata(L, narg); - lua_pop(L, 2); - return tuple; + + data = luaL_checkcdata(L, narg, &ctypeid); + if (ctypeid != CTID_CONST_STRUCT_TUPLE_REF) + return NULL; + + struct tuple *t = *(struct tuple **) data; + assert(t->refs); + return t; } static int @@ -164,9 +180,8 @@ lbox_tuple_slice(struct lua_State *L) static void luamp_encode_extension_box(struct lua_State *L, int idx, struct tbuf *b) { - if (lua_type(L, idx) == LUA_TUSERDATA && - lua_istuple(L, idx)) { - struct tuple *tuple = lua_checktuple(L, idx); + struct tuple *tuple = lua_istuple(L, idx); + if (tuple != NULL) { tuple_to_tbuf(tuple, b); return; } @@ -182,8 +197,7 @@ luamp_encode_extension_box(struct lua_State *L, int idx, struct tbuf *b) int luamp_encodestack(struct lua_State *L, struct tbuf *b, int first, int last) { - if (first == last && (lua_istable(L, first) || - (lua_isuserdata(L, first) && lua_istuple(L, first)))) { + if (first == last && (lua_istable(L, first) || lua_istuple(L, first))) { /* New format */ luamp_encode(L, b, first); return 1; @@ -442,46 +456,19 @@ lbox_tuple_index(struct lua_State *L) return 1; } -static int -lbox_tuple_tostring(struct lua_State *L) -{ - /* - * The method does next things: - * 1. Calls :unpack - * 2. Serializes the result using yaml - * 3. Strips start and end of yaml document symbols - */ - - /* unpack */ - lbox_tuple_totable(L); - - /* serialize */ - lua_replace(L, 1); - yamlL_encode(L); - - /* strip yaml tags */ - size_t len; - const char *str = lua_tolstring(L, -1, &len); - assert(strlen(str) == len); - const char *s = index(str, '['); - const char *e = rindex(str, ']'); - assert(s != NULL && e != NULL && s + 1 <= e); - lua_pushlstring(L, s, e - s + 1); - return 1; -} - void lbox_pushtuple(struct lua_State *L, struct tuple *tuple) { if (tuple) { - struct tuple **ptr = (struct tuple **) - lua_newuserdata(L, sizeof(*ptr)); - luaL_getmetatable(L, tuplelib_name); - lua_setmetatable(L, -2); + assert(CTID_CONST_STRUCT_TUPLE_REF != 0); + struct tuple **ptr = (struct tuple **) luaL_pushcdata(L, + CTID_CONST_STRUCT_TUPLE_REF, sizeof(struct tuple *)); *ptr = tuple; + lua_pushcfunction(L, lbox_tuple_gc); + luaL_setcdatagc(L, -2); tuple_ref(tuple, 1); } else { - lua_pushnil(L); + return lua_pushnil(L); } } @@ -535,23 +522,10 @@ lbox_tuple_pairs(struct lua_State *L) return 3; } - -/** tuple:bsize() - * - */ -static int -lbox_tuple_bsize(struct lua_State *L) -{ - struct tuple *tuple = lua_checktuple(L, 1); - lua_pushnumber(L, tuple->bsize); - return 1; -} - static const struct luaL_reg lbox_tuple_meta[] = { {"__gc", lbox_tuple_gc}, {"__len", lbox_tuple_len}, {"__index", lbox_tuple_index}, - {"__tostring", lbox_tuple_tostring}, {"next", lbox_tuple_next}, {"pairs", lbox_tuple_pairs}, {"slice", lbox_tuple_slice}, @@ -560,7 +534,6 @@ static const struct luaL_reg lbox_tuple_meta[] = { {"findall", lbox_tuple_findall}, {"unpack", lbox_tuple_unpack}, {"totable", lbox_tuple_totable}, - {"bsize", lbox_tuple_bsize}, {NULL, NULL} }; @@ -598,7 +571,11 @@ lua_totuple(struct lua_State *L, int first, int last) void box_lua_tuple_init(struct lua_State *L) { - luaL_register_type(L, tuplelib_name, lbox_tuple_meta); + /* export C functions to Lua */ + luaL_newmetatable(L, tuplelib_name); + luaL_register(L, NULL, lbox_tuple_meta); + /* save Lua/C functions to the global variable (cleaned by tuple.lua) */ + lua_setglobal(L, "cfuncs"); luaL_register_type(L, tuple_iteratorlib_name, lbox_tuple_iterator_meta); luaL_register(L, tuplelib_name, lbox_tuplelib); @@ -614,6 +591,13 @@ box_lua_tuple_init(struct lua_State *L) tuple_totable_mt_ref = luaL_ref(L, LUA_REGISTRYINDEX); assert(tuple_totable_mt_ref != 0); + if (luaL_dostring(L, tuple_lua)) + panic("Error loading Lua source %.160s...: %s", + tuple_lua, lua_tostring(L, -1)); + assert(lua_gettop(L) == 0); + + /* Get CTypeIDs */ + CTID_CONST_STRUCT_TUPLE_REF = luaL_ctypeid(L, "const struct tuple &"); + box_lua_slab_init(L); } - diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua new file mode 100644 index 0000000000000000000000000000000000000000..c2989d2897f30f87076755eb5865330f158b7ae1 --- /dev/null +++ b/src/box/lua/tuple.lua @@ -0,0 +1,57 @@ +-- tuple.lua (internal file) + +local ffi = require('ffi') +local yaml = require('yaml') + +ffi.cdef([[ +struct tuple +{ + uint32_t _version; + uint16_t _refs; + uint16_t _format_id; + uint32_t _bsize; + char data[0]; +} __attribute__((packed)); +]]) + +local builtin = ffi.C + +-- cfuncs table is set by C part +local methods = { + ["next"] = cfuncs.next; + ["pairs"] = cfuncs.pairs; + ["slice"] = cfuncs.slice; + ["transform"] = cfuncs.transform; + ["find"] = cfuncs.find; + ["findall"] = cfuncs.findall; + ["unpack"] = cfuncs.unpack; + ["totable"] = cfuncs.totable; + ["bsize"] = function(tuple) + return tonumber(tuple._bsize) + end +} + +local tuple_gc = cfuncs.__gc; + +local tuple_field = cfuncs.__index +ffi.metatype('struct tuple', { + __gc = tuple_gc; + __len = cfuncs.__len; + __tostring = function(tuple) + -- Unpack tuple, call yaml.encode, remove yaml header and footer + -- 5 = '---\n\n' (header), -6 = '\n...\n' (footer) + return yaml.encode(methods.totable(tuple)):sub(5, -6) + end; + __index = function(tuple, key) + if type(key) == "number" then + return tuple_field(tuple, key) + end + return methods[key] + end +}) + +-- Remove the global variable +cfuncs = nil + +-- export tuple_gc */ +box.tuple._gc = tuple_gc; diff --git a/src/lua/utils.c b/src/lua/utils.c index afcfe948ca33761be5af667fb952f2967b9b8cef..35ca2c10d54a72e3555f86df56c60c5c22b965fa 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -41,6 +41,8 @@ #include <lj_ctype.h> #include <lj_cdata.h> #include <lj_cconv.h> +#include <lj_lib.h> +#include <lj_tab.h> void * luaL_pushcdata(struct lua_State *L, uint32_t ctypeid, uint32_t size) @@ -79,6 +81,61 @@ luaL_checkcdata(struct lua_State *L, int idx, uint32_t *ctypeid) *ctypeid = cd->ctypeid; return (void *)cdataptr(cd); } + +uint32_t +luaL_ctypeid(struct lua_State *L, const char *ctypename) +{ + int idx = lua_gettop(L); + /* This function calls ffi.typeof to determine CDataType */ + + /* Get ffi.typeof function */ + luaL_loadstring(L, "return require('ffi').typeof"); + lua_call(L, 0, 1); + /* FFI must exist */ + assert(lua_gettop(L) == idx + 1 && lua_isfunction(L, idx + 1)); + /* Push the first argument to ffi.typeof */ + lua_pushstring(L, ctypename); + /* Call ffi.typeof() */ + lua_call(L, 1, 1); + /* Returned type must be LUA_TCDATA with CTID_CTYPEID */ + uint32_t ctypetypeid; + CTypeID ctypeid = *(CTypeID *)luaL_checkcdata(L, idx + 1, &ctypetypeid); + assert(ctypetypeid == CTID_CTYPEID); + + lua_settop(L, idx); + return ctypeid; +} + +int +luaL_setcdatagc(struct lua_State *L, int idx) +{ + if (idx < 0) + idx = lua_gettop(L) + idx + 1; + /* extracted from luajit/src/lib_ffi.c */ + TValue *o = L->base + idx - 1; + assert(o < L->top && tviscdata(o)); + GCcdata *cd = cdataV(o); + TValue *fin = lj_lib_checkany(L, lua_gettop(L)); + CTState *cts = ctype_cts(L); + GCtab *t = cts->finalizer; +#if !defined(NDEBUG) + CType *ct = ctype_raw(cts, cd->ctypeid); + assert(ctype_isptr(ct->info) || ctype_isstruct(ct->info) || + ctype_isrefarray(ct->info)); +#endif /* !defined(NDEBUG) */ + if (gcref(t->metatable)) { /* Update finalizer table, if still enabled. */ + copyTV(L, lj_tab_set(L, t, L->base + idx - 1), fin); + lj_gc_anybarriert(L, t); + if (!tvisnil(fin)) + cd->marked |= LJ_GC_CDATA_FIN; + else + cd->marked &= ~LJ_GC_CDATA_FIN; + } + lua_pop(L, 1); + + return 1; +} + #endif /* defined(LUAJIT) */ static void diff --git a/src/lua/utils.h b/src/lua/utils.h index 656083abc2ca0c2dbfcdd3da3170493f3ba07c32..55d32e77ad2a692db80742b09b9a71571e904162 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -74,6 +74,28 @@ luaL_pushcdata(struct lua_State *L, uint32_t ctypeid, uint32_t size); void * luaL_checkcdata(struct lua_State *L, int idx, uint32_t *ctypeid); +/** + * @brief Sets finalizer function on a cdata object. + * Equivalent to call ffi.gc(obj, function). + * Finalizer function must be on the top of the stack. + * @param L Lua State + * @param idx object + * @return 1 + */ +int +luaL_setcdatagc(struct lua_State *L, int idx); + +/** +* @brief Return CTypeID (FFI) of given СDATA type +* @param L Lua State +* @param ctypename С type name as string (e.g. "struct request" or "uint32_t") +* @sa luaL_pushcdata +* @sa luaL_checkcdata +* @return CTypeID +*/ +uint32_t +luaL_ctypeid(struct lua_State *L, const char *ctypename); + #endif /* defined(LUAJIT) */ /** A single value on the Lua stack. */ diff --git a/test/box/misc.result b/test/box/misc.result index 50709569789b3b4e97c15bff3284bbc74a03b826..d6884ceccc4dea7150d10f1d559a606eb0700d1d 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -493,3 +493,105 @@ space:delete{1} space:drop() --- ... +---------------- +-- # yaml encode/decode on cdata +---------------- +ffi = require('ffi') +--- +... +ffi.new('uint8_t', 128) +--- +- 128 +... +ffi.new('int8_t', -128) +--- +- -128 +... +ffi.new('uint16_t', 128) +--- +- 128 +... +ffi.new('int16_t', -128) +--- +- -128 +... +ffi.new('uint32_t', 128) +--- +- 128 +... +ffi.new('int32_t', -128) +--- +- -128 +... +ffi.new('uint64_t', 128) +--- +- 128 +... +ffi.new('int64_t', -128) +--- +- -128 +... +ffi.new('char', 128) +--- +- -128 +... +ffi.new('char', -128) +--- +- -128 +... +ffi.new('bool', true) +--- +- true +... +ffi.new('bool', false) +--- +- false +... +ffi.new('float', 1.23456) +--- +- 1.2345600128174 +... +ffi.new('float', 1e10) +--- +- 10000000000 +... +ffi.new('double', 1.23456) +--- +- 1.23456 +... +ffi.new('double', 1e10) +--- +- 10000000000 +... +ffi.cast('void *', 0) +--- +- null +... +ffi.cast('void *', 0xabcdef) +--- +- 'cdata<void *>: 0x00abcdef' +... +ffi.cdef([[struct test { int a; }; ]]) +--- +... +ffi.cast('struct test *', 0) +--- +- 'cdata<struct test *>: NULL' +... +--# setopt delimiter ';' +type(ffi.metatype('struct test', { + __index = { + totable = function(test) + return { 'yaml totable test = ' .. test.a } + end + } +})); +--- +- cdata +... +--# setopt delimiter '' +-- custom totable function will be called by yaml.encode +ffi.new('struct test', { a = 15 }) +--- +- - yaml totable test = 15 +... diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua index 417761580c4304c2d0c10ca98a9581ed92bb9817..6e2f96d8594a6cd8b7fc87706ed11e8ba0784b91 100644 --- a/test/box/misc.test.lua +++ b/test/box/misc.test.lua @@ -157,3 +157,47 @@ fifo_top(space, 1) space:delete{1} space:drop() + +---------------- +-- # yaml encode/decode on cdata +---------------- + +ffi = require('ffi') + +ffi.new('uint8_t', 128) +ffi.new('int8_t', -128) +ffi.new('uint16_t', 128) +ffi.new('int16_t', -128) +ffi.new('uint32_t', 128) +ffi.new('int32_t', -128) +ffi.new('uint64_t', 128) +ffi.new('int64_t', -128) + +ffi.new('char', 128) +ffi.new('char', -128) +ffi.new('bool', true) +ffi.new('bool', false) + +ffi.new('float', 1.23456) +ffi.new('float', 1e10) +ffi.new('double', 1.23456) +ffi.new('double', 1e10) + +ffi.cast('void *', 0) +ffi.cast('void *', 0xabcdef) + +ffi.cdef([[struct test { int a; }; ]]) +ffi.cast('struct test *', 0) + +--# setopt delimiter ';' +type(ffi.metatype('struct test', { + __index = { + totable = function(test) + return { 'yaml totable test = ' .. test.a } + end + } +})); + +--# setopt delimiter '' +-- custom totable function will be called by yaml.encode +ffi.new('struct test', { a = 15 }) diff --git a/test/box/net.box.result b/test/box/net.box.result index cbd5e4591315a382d824cc30f73cfa8c8718fc91..1870473c8020e8346bc91513f0d820c5545aadd8 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -185,7 +185,7 @@ tuple ... type(tuple) --- -- userdata +- cdata ... #tuple --- diff --git a/test/box/tuple.result b/test/box/tuple.result index 8e1900fec31c06933b6ba1828b70b930d2e51f5f..c7bfdd862d45994b569c054ea10fb54ccff743a0 100644 --- a/test/box/tuple.result +++ b/test/box/tuple.result @@ -311,8 +311,7 @@ t=space:insert{0, 8989} ... t[nil] --- -- error: '[string "return t[nil] "]:1: bad argument #2 to ''__index'' (string expected, - got nil)' +- null ... # test tuple iterators --- @@ -330,7 +329,7 @@ t:next(1) ... t:next(t) --- -- error: 'bad argument #2 to ''?'' (box.tuple.iterator expected, got userdata)' +- error: 'tuple.next(): bad arguments' ... t:next(t:next()) --- diff --git a/third_party/lua-yaml/lyaml.c b/third_party/lua-yaml/lyaml.cc similarity index 96% rename from third_party/lua-yaml/lyaml.c rename to third_party/lua-yaml/lyaml.cc index 3baa167bae1fa40f5fc53d5c96f314e4ba3b7ea3..b94ab2a013f94306ae599cdeea6a953353d35344 100644 --- a/third_party/lua-yaml/lyaml.c +++ b/third_party/lua-yaml/lyaml.cc @@ -31,6 +31,7 @@ #include <stdbool.h> #include <inttypes.h> +extern "C" { #include <lauxlib.h> #include <lua.h> #include <lualib.h> @@ -42,6 +43,7 @@ #include "yaml.h" #include "b64.h" +} /* extern "C" */ #include "lua/utils.h" /* configurable flags */ @@ -555,24 +557,32 @@ dump_node(struct lua_yaml_dumper *dumper) luaL_tofield(dumper->L, top, &field); /* Unknown type on the stack, try to call 'totable' from metadata */ - if (field.type == MP_EXT && lua_type(dumper->L, top) == LUA_TUSERDATA && - lua_getmetatable(dumper->L, top)) { - /* has metatable, try to call 'totable' and use return value */ - lua_pushliteral(dumper->L, "totable"); - lua_rawget(dumper->L, -2); - if (lua_isfunction(dumper->L, -1)) { - lua_pushvalue(dumper->L, -3); /* copy object itself */ - lua_call(dumper->L, 1, 1); - lua_replace(dumper->L, -3); - luaL_tofield(dumper->L, -1, &field); - } else { - lua_pop(dumper->L, 1); /* pop result */ + int type = lua_type(dumper->L, top); + if (field.type == MP_EXT && + (type == LUA_TUSERDATA || type == LUA_TCDATA)) { + /* try to call 'totable' method on udata/cdata */ + try { + /* + * LuaJIT specific: lua_getfield raises exception on + * cdata objects if field doesn't exist. + */ + lua_getfield(dumper->L, top, "totable"); + if (lua_isfunction(dumper->L, -1)) { + /* copy object itself */ + lua_pushvalue(dumper->L, top); + lua_call(dumper->L, 1, 1); + if (lua_istable(dumper->L, -1)) { + /* replace obj with the unpacked table*/ + lua_replace(dumper->L, top); + luaL_tofield(dumper->L, -1, &field); + } + } + } catch (...) { + /* ignore lua_getfield exceptions */ } - lua_pop(dumper->L, 1); /* pop metatable */ + lua_settop(dumper->L, top); /* remove temporary objects */ } - luaL_tofield(dumper->L, top, &field); - /* Still have unknown type on the stack, * try to call 'tostring' */ if (field.type == MP_EXT) { @@ -846,7 +856,7 @@ static int l_null(lua_State *L) { return 1; } -LUALIB_API int luaopen_yaml(lua_State *L) { +extern "C" int luaopen_yaml(lua_State *L) { const luaL_reg yamllib[] = { { "decode", l_load }, { "encode", l_dump }, @@ -859,6 +869,6 @@ LUALIB_API int luaopen_yaml(lua_State *L) { return 1; } -LUALIB_API int yamlL_encode(lua_State *L) { +extern "C" int yamlL_encode(lua_State *L) { return l_dump(L); }