From a5bb3ee2af6ab06942e511f1ac6aa810d2e1fa5d Mon Sep 17 00:00:00 2001 From: Pelanyo Kamara Date: Mon, 22 Nov 2021 15:42:11 +0000 Subject: [PATCH 1/5] Add luaL_checkboolean and luaL_optboolean (#221) Co-authored-by: Arseny Kapoulkine --- VM/include/lualib.h | 3 +++ VM/src/laux.cpp | 16 ++++++++++++++++ tests/Conformance.test.cpp | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/VM/include/lualib.h b/VM/include/lualib.h index fa836955..54b008ff 100644 --- a/VM/include/lualib.h +++ b/VM/include/lualib.h @@ -25,6 +25,9 @@ LUALIB_API const char* luaL_optlstring(lua_State* L, int numArg, const char* def LUALIB_API double luaL_checknumber(lua_State* L, int numArg); LUALIB_API double luaL_optnumber(lua_State* L, int nArg, double def); +LUALIB_API int luaL_checkboolean(lua_State* L, int narg); +LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def); + LUALIB_API int luaL_checkinteger(lua_State* L, int numArg); LUALIB_API int luaL_optinteger(lua_State* L, int nArg, int def); LUALIB_API unsigned luaL_checkunsigned(lua_State* L, int numArg); diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 2a684ee4..5257271a 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -183,6 +183,22 @@ LUALIB_API double luaL_optnumber(lua_State* L, int narg, double def) return luaL_opt(L, luaL_checknumber, narg, def); } +LUALIB_API int luaL_checkboolean(lua_State* L, int narg) +{ + // This checks specifically for boolean values, ignoring + // all other truthy/falsy values. If the desired result + // is true if value is present then lua_toboolean should + // directly be used instead. + if (!lua_isboolean(L, narg)) + tag_error(L, narg, LUA_TBOOLEAN); + return lua_toboolean(L, narg); +} + +LUALIB_API int luaL_optboolean(lua_State* L, int narg, int def) +{ + return luaL_opt(L, luaL_checkboolean, narg, def); +} + LUALIB_API int luaL_checkinteger(lua_State* L, int narg) { int isnum; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index e495a213..22977219 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -524,7 +524,7 @@ TEST_CASE("Debugger") L, [](lua_State* L) -> int { int line = luaL_checkinteger(L, 1); - bool enabled = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : true; + bool enabled = luaL_optboolean(L, 2, true); lua_Debug ar = {}; lua_getinfo(L, 1, "f", &ar); From 2740f69f326a9b18e622571084bf0a03beff13b2 Mon Sep 17 00:00:00 2001 From: petrihakkinen Date: Mon, 22 Nov 2021 17:42:33 +0200 Subject: [PATCH 2/5] Expand vectors to 4 components using compile time switch (#214) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Petri Häkkinen Co-authored-by: Arseny Kapoulkine --- VM/include/lua.h | 4 ++++ VM/include/luaconf.h | 4 ++++ VM/src/lapi.cpp | 13 +++++++++++-- VM/src/laux.cpp | 4 ++++ VM/src/lbuiltins.cpp | 12 +++++++++++- VM/src/lmem.cpp | 8 +++++++- VM/src/lnumutils.h | 8 ++++++++ VM/src/lobject.cpp | 2 +- VM/src/lobject.h | 23 ++++++++++++++++++----- VM/src/ltable.cpp | 19 +++++++++++++------ VM/src/lvmexecute.cpp | 30 ++++++++++++++++++------------ VM/src/lvmutils.cpp | 18 +++++++++--------- tests/Conformance.test.cpp | 11 +++++++++++ tests/conformance/vector.lua | 31 ++++++++++++++++++++++++++----- 14 files changed, 145 insertions(+), 42 deletions(-) diff --git a/VM/include/lua.h b/VM/include/lua.h index 1568d191..ebe7bbc5 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -159,7 +159,11 @@ LUA_API void lua_pushnil(lua_State* L); LUA_API void lua_pushnumber(lua_State* L, double n); LUA_API void lua_pushinteger(lua_State* L, int n); LUA_API void lua_pushunsigned(lua_State* L, unsigned n); +#if LUA_VECTOR_SIZE == 4 +LUA_API void lua_pushvector(lua_State* L, float x, float y, float z, float w); +#else LUA_API void lua_pushvector(lua_State* L, float x, float y, float z); +#endif LUA_API void lua_pushlstring(lua_State* L, const char* s, size_t l); LUA_API void lua_pushstring(lua_State* L, const char* s); LUA_API const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp); diff --git a/VM/include/luaconf.h b/VM/include/luaconf.h index aa008a24..cce70a20 100644 --- a/VM/include/luaconf.h +++ b/VM/include/luaconf.h @@ -122,3 +122,7 @@ void* s; \ long l; \ } + +#define LUA_VECTOR_SIZE 3 /* must be 3 or 4 */ + +#define LUA_EXTRA_SIZE LUA_VECTOR_SIZE - 2 diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index a79ba0d4..832849ac 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -550,12 +550,21 @@ void lua_pushunsigned(lua_State* L, unsigned u) return; } -void lua_pushvector(lua_State* L, float x, float y, float z) +#if LUA_VECTOR_SIZE == 4 +void lua_pushvector(lua_State* L, float x, float y, float z, float w) { - setvvalue(L->top, x, y, z); + setvvalue(L->top, x, y, z, w); api_incr_top(L); return; } +#else +void lua_pushvector(lua_State* L, float x, float y, float z) +{ + setvvalue(L->top, x, y, z, 0.0f); + api_incr_top(L); + return; +} +#endif void lua_pushlstring(lua_State* L, const char* s, size_t len) { diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 5257271a..c5133a58 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -478,7 +478,11 @@ LUALIB_API const char* luaL_tolstring(lua_State* L, int idx, size_t* len) case LUA_TVECTOR: { const float* v = lua_tovector(L, idx); +#if LUA_VECTOR_SIZE == 4 + lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2], v[3]); +#else lua_pushfstring(L, LUA_NUMBER_FMT ", " LUA_NUMBER_FMT ", " LUA_NUMBER_FMT, v[0], v[1], v[2]); +#endif break; } default: diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 9ab57ac9..34e9ebc1 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -1018,13 +1018,23 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St static int luauF_vector(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { +#if LUA_VECTOR_SIZE == 4 + if (nparams >= 4 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1) && ttisnumber(args + 2)) +#else if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) +#endif { double x = nvalue(arg0); double y = nvalue(args); double z = nvalue(args + 1); - setvvalue(res, float(x), float(y), float(z)); +#if LUA_VECTOR_SIZE == 4 + double w = nvalue(args + 2); + setvvalue(res, float(x), float(y), float(z), float(w)); +#else + setvvalue(res, float(x), float(y), float(z), 0.0f); +#endif + return 1; } diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index d8b265cb..9f9d4a98 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -33,11 +33,17 @@ #define ABISWITCH(x64, ms32, gcc32) (sizeof(void*) == 8 ? x64 : ms32) #endif +#if LUA_VECTOR_SIZE == 4 +static_assert(sizeof(TValue) == ABISWITCH(24, 24, 24), "size mismatch for value"); +static_assert(sizeof(LuaNode) == ABISWITCH(48, 48, 48), "size mismatch for table entry"); +#else static_assert(sizeof(TValue) == ABISWITCH(16, 16, 16), "size mismatch for value"); +static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); +#endif + static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(24, 16, 16), "size mismatch for userdata header"); static_assert(sizeof(Table) == ABISWITCH(56, 36, 36), "size mismatch for table header"); -static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table entry"); const size_t kSizeClasses = LUA_SIZECLASSES; const size_t kMaxSmallSize = 512; diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index 43f8014b..67f832dc 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -18,12 +18,20 @@ inline bool luai_veceq(const float* a, const float* b) { +#if LUA_VECTOR_SIZE == 4 + return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; +#else return a[0] == b[0] && a[1] == b[1] && a[2] == b[2]; +#endif } inline bool luai_vecisnan(const float* a) { +#if LUA_VECTOR_SIZE == 4 + return a[0] != a[0] || a[1] != a[1] || a[2] != a[2] || a[3] != a[3]; +#else return a[0] != a[0] || a[1] != a[1] || a[2] != a[2]; +#endif } LUAU_FASTMATH_BEGIN diff --git a/VM/src/lobject.cpp b/VM/src/lobject.cpp index bf13e6e9..370c7b28 100644 --- a/VM/src/lobject.cpp +++ b/VM/src/lobject.cpp @@ -15,7 +15,7 @@ -const TValue luaO_nilobject_ = {{NULL}, LUA_TNIL}; +const TValue luaO_nilobject_ = {{NULL}, {0}, LUA_TNIL}; int luaO_log2(unsigned int x) { diff --git a/VM/src/lobject.h b/VM/src/lobject.h index c5f2e2f4..ba040af6 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -47,7 +47,7 @@ typedef union typedef struct lua_TValue { Value value; - int extra; + int extra[LUA_EXTRA_SIZE]; int tt; } TValue; @@ -105,7 +105,19 @@ typedef struct lua_TValue i_o->tt = LUA_TNUMBER; \ } -#define setvvalue(obj, x, y, z) \ +#if LUA_VECTOR_SIZE == 4 +#define setvvalue(obj, x, y, z, w) \ + { \ + TValue* i_o = (obj); \ + float* i_v = i_o->value.v; \ + i_v[0] = (x); \ + i_v[1] = (y); \ + i_v[2] = (z); \ + i_v[3] = (w); \ + i_o->tt = LUA_TVECTOR; \ + } +#else +#define setvvalue(obj, x, y, z, w) \ { \ TValue* i_o = (obj); \ float* i_v = i_o->value.v; \ @@ -114,6 +126,7 @@ typedef struct lua_TValue i_v[2] = (z); \ i_o->tt = LUA_TVECTOR; \ } +#endif #define setpvalue(obj, x) \ { \ @@ -364,7 +377,7 @@ typedef struct Closure typedef struct TKey { ::Value value; - int extra; + int extra[LUA_EXTRA_SIZE]; unsigned tt : 4; int next : 28; /* for chaining */ } TKey; @@ -381,7 +394,7 @@ typedef struct LuaNode LuaNode* n_ = (node); \ const TValue* i_o = (obj); \ n_->key.value = i_o->value; \ - n_->key.extra = i_o->extra; \ + memcpy(n_->key.extra, i_o->extra, sizeof(n_->key.extra)); \ n_->key.tt = i_o->tt; \ checkliveness(L->global, i_o); \ } @@ -392,7 +405,7 @@ typedef struct LuaNode TValue* i_o = (obj); \ const LuaNode* n_ = (node); \ i_o->value = n_->key.value; \ - i_o->extra = n_->key.extra; \ + memcpy(i_o->extra, n_->key.extra, sizeof(i_o->extra)); \ i_o->tt = n_->key.tt; \ checkliveness(L->global, i_o); \ } diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index 07d22d59..0b55fcea 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -31,18 +31,19 @@ LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false) #define MAXSIZE (1 << MAXBITS) static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect"); + // TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case -static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); -static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); +static_assert(TKey{{NULL}, {0}, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt"); +static_assert(TKey{{NULL}, {0}, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next"); +static_assert(TKey{{NULL}, {0}, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next"); // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->flags = 0 // empty hash data points to dummynode so that we can always dereference it const LuaNode luaH_dummynode = { - {{NULL}, 0, LUA_TNIL}, /* value */ - {{NULL}, 0, LUA_TNIL, 0} /* key */ + {{NULL}, {0}, LUA_TNIL}, /* value */ + {{NULL}, {0}, LUA_TNIL, 0} /* key */ }; #define dummynode (&luaH_dummynode) @@ -96,7 +97,7 @@ static LuaNode* hashnum(const Table* t, double n) static LuaNode* hashvec(const Table* t, const float* v) { - unsigned int i[3]; + unsigned int i[LUA_VECTOR_SIZE]; memcpy(i, v, sizeof(i)); // convert -0 to 0 to make sure they hash to the same value @@ -112,6 +113,12 @@ static LuaNode* hashvec(const Table* t, const float* v) // Optimized Spatial Hashing for Collision Detection of Deformable Objects unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791); +#if LUA_VECTOR_SIZE == 4 + i[3] = (i[3] == 0x8000000) ? 0 : i[3]; + i[3] ^= i[3] >> 17; + h ^= i[3] * 39916801; +#endif + return hashpow2(t, h); } diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index eed2862b..bf8d493e 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -601,7 +601,13 @@ static void luau_execute(lua_State* L) const char* name = getstr(tsvalue(kv)); int ic = (name[0] | ' ') - 'x'; - if (unsigned(ic) < 3 && name[1] == '\0') +#if LUA_VECTOR_SIZE == 4 + // 'w' is before 'x' in ascii, so ic is -1 when indexing with 'w' + if (ic == -1) + ic = 3; +#endif + + if (unsigned(ic) < LUA_VECTOR_SIZE && name[1] == '\0') { setnvalue(ra, rb->value.v[ic]); VM_NEXT(); @@ -1526,7 +1532,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); + setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]); VM_NEXT(); } else @@ -1572,7 +1578,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); + setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]); VM_NEXT(); } else @@ -1618,21 +1624,21 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(rc)); - setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); + setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc); VM_NEXT(); } else if (ttisvector(rb) && ttisvector(rc)) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); + setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]); VM_NEXT(); } else if (ttisnumber(rb) && ttisvector(rc)) { float vb = cast_to(float, nvalue(rb)); const float* vc = rc->value.v; - setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2]); + setvvalue(ra, vb * vc[0], vb * vc[1], vb * vc[2], vb * vc[3]); VM_NEXT(); } else @@ -1679,21 +1685,21 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(rc)); - setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); + setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc); VM_NEXT(); } else if (ttisvector(rb) && ttisvector(rc)) { const float* vb = rb->value.v; const float* vc = rc->value.v; - setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); + setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); VM_NEXT(); } else if (ttisnumber(rb) && ttisvector(rc)) { float vb = cast_to(float, nvalue(rb)); const float* vc = rc->value.v; - setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2]); + setvvalue(ra, vb / vc[0], vb / vc[1], vb / vc[2], vb / vc[3]); VM_NEXT(); } else @@ -1826,7 +1832,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(kv)); - setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc); + setvvalue(ra, vb[0] * vc, vb[1] * vc, vb[2] * vc, vb[3] * vc); VM_NEXT(); } else @@ -1872,7 +1878,7 @@ static void luau_execute(lua_State* L) { const float* vb = rb->value.v; float vc = cast_to(float, nvalue(kv)); - setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc); + setvvalue(ra, vb[0] / vc, vb[1] / vc, vb[2] / vc, vb[3] / vc); VM_NEXT(); } else @@ -2037,7 +2043,7 @@ static void luau_execute(lua_State* L) else if (ttisvector(rb)) { const float* vb = rb->value.v; - setvvalue(ra, -vb[0], -vb[1], -vb[2]); + setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); VM_NEXT(); } else diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index f52e8e74..740a4cfd 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -401,19 +401,19 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_ADD: - setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2]); + setvvalue(ra, vb[0] + vc[0], vb[1] + vc[1], vb[2] + vc[2], vb[3] + vc[3]); return; case TM_SUB: - setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2]); + setvvalue(ra, vb[0] - vc[0], vb[1] - vc[1], vb[2] - vc[2], vb[3] - vc[3]); return; case TM_MUL: - setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2]); + setvvalue(ra, vb[0] * vc[0], vb[1] * vc[1], vb[2] * vc[2], vb[3] * vc[3]); return; case TM_DIV: - setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2]); + setvvalue(ra, vb[0] / vc[0], vb[1] / vc[1], vb[2] / vc[2], vb[3] / vc[3]); return; case TM_UNM: - setvvalue(ra, -vb[0], -vb[1], -vb[2]); + setvvalue(ra, -vb[0], -vb[1], -vb[2], -vb[3]); return; default: break; @@ -430,10 +430,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_MUL: - setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc); + setvvalue(ra, vb[0] * nc, vb[1] * nc, vb[2] * nc, vb[3] * nc); return; case TM_DIV: - setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc); + setvvalue(ra, vb[0] / nc, vb[1] / nc, vb[2] / nc, vb[3] / nc); return; default: break; @@ -451,10 +451,10 @@ void luaV_doarith(lua_State* L, StkId ra, const TValue* rb, const TValue* rc, TM switch (op) { case TM_MUL: - setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2]); + setvvalue(ra, nb * vc[0], nb * vc[1], nb * vc[2], nb * vc[3]); return; case TM_DIV: - setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2]); + setvvalue(ra, nb / vc[0], nb / vc[1], nb / vc[2], nb / vc[3]); return; default: break; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 22977219..a573ae42 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -67,7 +67,12 @@ static int lua_vector(lua_State* L) double y = luaL_checknumber(L, 2); double z = luaL_checknumber(L, 3); +#if LUA_VECTOR_SIZE == 4 + double w = luaL_optnumber(L, 4, 0.0); + lua_pushvector(L, float(x), float(y), float(z), float(w)); +#else lua_pushvector(L, float(x), float(y), float(z)); +#endif return 1; } @@ -373,11 +378,17 @@ TEST_CASE("Pack") TEST_CASE("Vector") { + ScopedFastFlag sff{"LuauIfElseExpressionBaseSupport", true}; + runConformance("vector.lua", [](lua_State* L) { lua_pushcfunction(L, lua_vector, "vector"); lua_setglobal(L, "vector"); +#if LUA_VECTOR_SIZE == 4 + lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); +#else lua_pushvector(L, 0.0f, 0.0f, 0.0f); +#endif luaL_newmetatable(L, "vector"); lua_pushstring(L, "__index"); diff --git a/tests/conformance/vector.lua b/tests/conformance/vector.lua index 620f646a..7d18bda3 100644 --- a/tests/conformance/vector.lua +++ b/tests/conformance/vector.lua @@ -1,6 +1,9 @@ -- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details print('testing vectors') +-- detect vector size +local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3 + -- equality assert(vector(1, 2, 3) == vector(1, 2, 3)) assert(vector(0, 1, 2) == vector(-0, 1, 2)) @@ -13,8 +16,14 @@ assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4))) -- type & tostring assert(type(vector(1, 2, 3)) == "vector") -assert(tostring(vector(1, 2, 3)) == "1, 2, 3") -assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") + +if vector_size == 4 then + assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4") + assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0") +else + assert(tostring(vector(1, 2, 3)) == "1, 2, 3") + assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") +end local t = {} @@ -42,12 +51,19 @@ assert(8 * vector(8, 16, 24) == vector(64, 128, 192)); assert(vector(1, 2, 4) * '8' == vector(8, 16, 32)); assert('8' * vector(8, 16, 24) == vector(64, 128, 192)); -assert(vector(1, 2, 4) / vector(8, 16, 24) == vector(1/8, 2/16, 4/24)); +if vector_size == 4 then + assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32)); + assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); + assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); +else + assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24)); + assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3)); + assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3)); +end + assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2)); assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8)); -assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3)); assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); -assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3)); assert(-vector(1, 2, 4) == vector(-1, -2, -4)); @@ -71,4 +87,9 @@ assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == fal -- make sure we cover both builtin and C impl assert(vector(1, 2, 4) == vector("1", "2", "4")) +-- additional checks for 4-component vectors +if vector_size == 4 then + assert(vector(1, 2, 3, 4).w == 4) +end + return 'OK' From 5961261a1ce0a5eeb180b1ac3ea8718b2bec0be7 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 22 Nov 2021 09:59:15 -0800 Subject: [PATCH 3/5] Add web workflow to build Repl with Emscripten (#222) This also separates Emscripten build into a new target / source to make it more decoupled. --- .github/workflows/build.yml | 19 ++++++ .github/workflows/release.yml | 23 ++++++++ CLI/Repl.cpp | 39 ------------- CLI/Web.cpp | 106 ++++++++++++++++++++++++++++++++++ CMakeLists.txt | 57 +++++++++--------- Sources.cmake | 6 ++ 6 files changed, 184 insertions(+), 66 deletions(-) create mode 100644 CLI/Web.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 506a4c89..430ac0cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,3 +83,22 @@ jobs: with: name: coverage path: coverage + + web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + repository: emscripten-core/emsdk + path: emsdk + - name: emsdk install + run: | + cd emsdk + ./emsdk install latest + ./emsdk activate latest + - name: make + run: | + source emsdk/emsdk_env.sh + emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release + make -j2 Luau.Web diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2fd8d3d5..e5cd952b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,3 +33,26 @@ jobs: with: name: luau-${{matrix.os}} path: Release\luau*.exe + + web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + repository: emscripten-core/emsdk + path: emsdk + - name: emsdk install + run: | + cd emsdk + ./emsdk install latest + ./emsdk activate latest + - name: make + run: | + source emsdk/emsdk_env.sh + emcmake cmake . -DLUAU_BUILD_WEB=ON -DCMAKE_BUILD_TYPE=Release + make -j2 Luau.Web + - uses: actions/upload-artifact@v2 + with: + name: Luau.Web.js + path: Luau.Web.js diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index 410674fa..89a6037b 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -198,11 +198,6 @@ static std::string runCode(lua_State* L, const std::string& source) error += "\nstack backtrace:\n"; error += lua_debugtrace(T); -#ifdef __EMSCRIPTEN__ - // nicer formatting for errors in web repl - error = "Error:" + error; -#endif - fprintf(stdout, "%s", error.c_str()); } @@ -210,39 +205,6 @@ static std::string runCode(lua_State* L, const std::string& source) return std::string(); } -#ifdef __EMSCRIPTEN__ -extern "C" -{ - const char* executeScript(const char* source) - { - // setup flags - for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) - if (strncmp(flag->name, "Luau", 4) == 0) - flag->value = true; - - // create new state - std::unique_ptr globalState(luaL_newstate(), lua_close); - lua_State* L = globalState.get(); - - // setup state - setupState(L); - - // sandbox thread - luaL_sandboxthread(L); - - // static string for caching result (prevents dangling ptr on function exit) - static std::string result; - - // run code + collect error - result = runCode(L, source); - - return result.empty() ? NULL : result.c_str(); - } -} -#endif - -// Excluded from emscripten compilation to avoid -Wunused-function errors. -#ifndef __EMSCRIPTEN__ static void completeIndexer(lua_State* L, const char* editBuffer, size_t start, std::vector& completions) { std::string_view lookup = editBuffer + start; @@ -564,5 +526,4 @@ int main(int argc, char** argv) return failed; } } -#endif diff --git a/CLI/Web.cpp b/CLI/Web.cpp new file mode 100644 index 00000000..cf5c831e --- /dev/null +++ b/CLI/Web.cpp @@ -0,0 +1,106 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "lua.h" +#include "lualib.h" +#include "luacode.h" + +#include "Luau/Common.h" + +#include + +#include + +static void setupState(lua_State* L) +{ + luaL_openlibs(L); + + luaL_sandbox(L); +} + +static std::string runCode(lua_State* L, const std::string& source) +{ + size_t bytecodeSize = 0; + char* bytecode = luau_compile(source.data(), source.length(), nullptr, &bytecodeSize); + int result = luau_load(L, "=stdin", bytecode, bytecodeSize, 0); + free(bytecode); + + if (result != 0) + { + size_t len; + const char* msg = lua_tolstring(L, -1, &len); + + std::string error(msg, len); + lua_pop(L, 1); + + return error; + } + + lua_State* T = lua_newthread(L); + + lua_pushvalue(L, -2); + lua_remove(L, -3); + lua_xmove(L, T, 1); + + int status = lua_resume(T, NULL, 0); + + if (status == 0) + { + int n = lua_gettop(T); + + if (n) + { + luaL_checkstack(T, LUA_MINSTACK, "too many results to print"); + lua_getglobal(T, "print"); + lua_insert(T, 1); + lua_pcall(T, n, 0, 0); + } + } + else + { + std::string error; + + if (status == LUA_YIELD) + { + error = "thread yielded unexpectedly"; + } + else if (const char* str = lua_tostring(T, -1)) + { + error = str; + } + + error += "\nstack backtrace:\n"; + error += lua_debugtrace(T); + + error = "Error:" + error; + + fprintf(stdout, "%s", error.c_str()); + } + + lua_pop(L, 1); + return std::string(); +} + +extern "C" const char* executeScript(const char* source) +{ + // setup flags + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + + // create new state + std::unique_ptr globalState(luaL_newstate(), lua_close); + lua_State* L = globalState.get(); + + // setup state + setupState(L); + + // sandbox thread + luaL_sandboxthread(L); + + // static string for caching result (prevents dangling ptr on function exit) + static std::string result; + + // run code + collect error + result = runCode(L, source); + + return result.empty() ? NULL : result.c_str(); +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c69521e..bafc59e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ project(Luau LANGUAGES CXX) option(LUAU_BUILD_CLI "Build CLI" ON) option(LUAU_BUILD_TESTS "Build tests" ON) +option(LUAU_BUILD_WEB "Build Web module" OFF) option(LUAU_WERROR "Warnings as errors" OFF) add_library(Luau.Ast STATIC) @@ -18,26 +19,22 @@ add_library(Luau.VM STATIC) if(LUAU_BUILD_CLI) add_executable(Luau.Repl.CLI) - if(NOT EMSCRIPTEN) - add_executable(Luau.Analyze.CLI) - else() - # add -fexceptions for emscripten to allow exceptions to be caught in C++ - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") - endif() + add_executable(Luau.Analyze.CLI) # This also adds target `name` on Linux/macOS and `name.exe` on Windows set_target_properties(Luau.Repl.CLI PROPERTIES OUTPUT_NAME luau) - - if(NOT EMSCRIPTEN) - set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze) - endif() + set_target_properties(Luau.Analyze.CLI PROPERTIES OUTPUT_NAME luau-analyze) endif() -if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) +if(LUAU_BUILD_TESTS) add_executable(Luau.UnitTest) add_executable(Luau.Conformance) endif() +if(LUAU_BUILD_WEB) + add_executable(Luau.Web) +endif() + include(Sources.cmake) target_compile_features(Luau.Ast PUBLIC cxx_std_17) @@ -72,16 +69,18 @@ if(LUAU_WERROR) endif() endif() +if(LUAU_BUILD_WEB) + # add -fexceptions for emscripten to allow exceptions to be caught in C++ + list(APPEND LUAU_OPTIONS -fexceptions) +endif() + target_compile_options(Luau.Ast PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.Analysis PRIVATE ${LUAU_OPTIONS}) target_compile_options(Luau.VM PRIVATE ${LUAU_OPTIONS}) if(LUAU_BUILD_CLI) target_compile_options(Luau.Repl.CLI PRIVATE ${LUAU_OPTIONS}) - - if(NOT EMSCRIPTEN) - target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) - endif() + target_compile_options(Luau.Analyze.CLI PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.Repl.CLI PRIVATE extern) target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.VM) @@ -93,20 +92,10 @@ if(LUAU_BUILD_CLI) endif() endif() - if(NOT EMSCRIPTEN) - target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis) - endif() - - if(EMSCRIPTEN) - # declare exported functions to emscripten - target_link_options(Luau.Repl.CLI PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -fexceptions) - - # custom output directory for wasm + js file - set_target_properties(Luau.Repl.CLI PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/assets/luau) - endif() + target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis) endif() -if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) +if(LUAU_BUILD_TESTS) target_compile_options(Luau.UnitTest PRIVATE ${LUAU_OPTIONS}) target_include_directories(Luau.UnitTest PRIVATE extern) target_link_libraries(Luau.UnitTest PRIVATE Luau.Analysis Luau.Compiler) @@ -115,3 +104,17 @@ if(LUAU_BUILD_TESTS AND NOT EMSCRIPTEN) target_include_directories(Luau.Conformance PRIVATE extern) target_link_libraries(Luau.Conformance PRIVATE Luau.Analysis Luau.Compiler Luau.VM) endif() + +if(LUAU_BUILD_WEB) + target_compile_options(Luau.Web PRIVATE ${LUAU_OPTIONS}) + target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM) + + # declare exported functions to emscripten + target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']) + + # add -fexceptions for emscripten to allow exceptions to be caught in C++ + target_link_options(Luau.Web PRIVATE -fexceptions) + + # the output is a single .js file with an embedded wasm blob + target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1) +endif() diff --git a/Sources.cmake b/Sources.cmake index 23b931c6..440cc3ec 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -224,3 +224,9 @@ if(TARGET Luau.Conformance) tests/Conformance.test.cpp tests/main.cpp) endif() + +if(TARGET Luau.Web) + # Luau.Web Sources + target_sources(Luau.Web PRIVATE + CLI/Web.cpp) +endif() From a26024fb4b6dd9b02f22ab369dbaa839e9d0a525 Mon Sep 17 00:00:00 2001 From: Tiffany Bennett Date: Mon, 22 Nov 2021 12:54:27 -0800 Subject: [PATCH 4/5] Use latest release url (#227) --- docs/_includes/repl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_includes/repl.html b/docs/_includes/repl.html index 1ff81fb8..9d9b5895 100644 --- a/docs/_includes/repl.html +++ b/docs/_includes/repl.html @@ -47,4 +47,4 @@ } } - + From 6958716ccd3b1e377c9682b393e1d84309a5b896 Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:59:38 -0800 Subject: [PATCH 5/5] RFC: String interpolation (#165) --- rfcs/syntax-string-interpolation.md | 142 ++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 rfcs/syntax-string-interpolation.md diff --git a/rfcs/syntax-string-interpolation.md b/rfcs/syntax-string-interpolation.md new file mode 100644 index 00000000..208143a0 --- /dev/null +++ b/rfcs/syntax-string-interpolation.md @@ -0,0 +1,142 @@ +# String interpolation + +## Summary + +New string interpolation syntax. + +## Motivation + +The problems with `string.format` are many. + +1. Must be exact about the types and its corresponding value. +2. Using `%d` is the idiomatic default for most people, but this loses precision. + * `%d` casts the number into `long long`, which has a lower max value than `double` and does not support decimals. + * `%f` by default will format to the millionths, e.g. `5.5` is `5.500000`. + * `%g` by default will format up to the hundred thousandths, e.g. `5.5` is `5.5` and `5.5312389` is `5.53123`. It will also convert the number to scientific notation when it encounters a number equal to or greater than 10^6. + * To not lose too much precision, you need to use `%s`, but even so the type checker assumes you actually wanted strings. +3. No support for `boolean`. You must use `%s` **and** call `tostring`. +4. No support for values implementing the `__tostring` metamethod. +5. Using `%` is in itself a dangerous operation within `string.format`. + * `"Your health is %d% so you need to heal up."` causes a runtime error because `% so` is actually parsed as `(%s)o` and now requires a corresponding string. +6. Having to use parentheses around string literals just to call a method of it. + +## Design + +To fix all of those issues, we need to do a few things. + +1. A new string interpolation expression (fixes #5, #6) +2. Extend `string.format` to accept values of arbitrary types (fixes #1, #2, #3, #4) + +Because we care about backward compatibility, we need some new syntax in order to not change the meaning of existing strings. There are a few components of this new expression: + +1. A string chunk (`` `...{ ``, `}...{`, and `` }...` ``) where `...` is a range of 0 to many characters. + * `\` escapes `` ` ``, `{`, and itself `\`. + * Restriction: the string interpolation literal must have at least one value to interpolate. We do not need 3 ways to express a single line string literal. + * The pairs must be on the same line (unless a `\` escapes the newline) but expressions needn't be on the same line. +2. An expression between the braces. This is the value that will be interpolated into the string. +3. Formatting specification may follow after the expression, delimited by an unambiguous character. + * Restriction: the formatting specification must be constant at parse time. + * In the absence of an explicit formatting specification, the `%*` token will be used. + * For now, we explicitly reject any formatting specification syntax. A future extension may be introduced to extend the syntax with an optional specification. + +To put the above into formal EBNF grammar: + +``` +stringinterp ::= exp { exp} +``` + +Which, in actual Luau code, will look like the following: + +``` +local world = "world" +print(`Hello {world}!`) +--> Hello world! + +local combo = {5, 2, 8, 9} +print(`The lock combinations are: {table.concat(combo, ", ")}`) +--> The lock combinations are: 5, 2, 8, 9 + +local set1 = Set.new({0, 1, 3}) +local set2 = Set.new({0, 5, 4}) +print(`{set1} ∪ {set2} = {Set.union(set1, set2)}`) +--> {0, 1, 3} ∪ {0, 5, 4} = {0, 1, 3, 4, 5} + +-- For illustrative purposes. These are illegal specifically because they don't interpolate anything. +print(`Some example escaping the braces \{like so}`) +print(`backslash \ that escapes the space is not a part of the string...`) +print(`backslash \\ will escape the second backslash...`) +print(`Some text that also includes \`...`) +--> Some example escaping the braces {like so} +--> backslash that escapes the space is not a part of the string... +--> backslash \ will escape the second backslash... +--> Some text that also includes `... +``` + +As for how newlines are handled, they are handled the same as other string literals. Any text between the `{}` delimiters are not considered part of the string, hence newlines are OK. The main thing is that one opening pair will scan until either a closing pair is encountered, or an unescaped newline. + +``` +local name = "Luau" + +print(`Welcome to { + name +}!`) +--> Welcome to Luau! + +print(`Welcome to \ +{name}!`) +--> Welcome to +-- Luau! +``` + +This expression will not be allowed to come after a `prefixexp`. I believe this is fully additive, so a future RFC may allow this. So for now, we explicitly reject the following: + +``` +local name = "world" +print`Hello {name}` +``` + +Since the string interpolation expression is going to be lowered into a `string.format` call, we'll also need to extend `string.format`. The bare minimum to support the lowering is to add a new token whose definition is to perform a `tostring` call. `%*` is currently an invalid token, so this is a backward compatible extension. This RFC shall define `%*` to have the same behavior as if `tostring` was called. + +```lua +print(string.format("%* %*", 1, 2)) +--> 1 2 +``` + +The offset must always be within bound of the numbers of values passed to `string.format`. + +```lua +local function return_one_thing() return "hi" end +local function return_two_nils() return nil, nil end + +print(string.format("%*", return_one_thing())) +--> "hi" + +print(string.format("%*", Set.new({1, 2, 3}))) +--> {1, 2, 3} + +print(string.format("%* %*", return_two_nils())) +--> nil nil + +print(string.format("%* %* %*", return_two_nils())) +--> error: value #3 is missing, got 2 +``` + +## Drawbacks + +If we want to use backticks for other purposes, it may introduce some potential ambiguity. One option to solve that is to only ever produce string interpolation tokens from the context of an expression. This is messy but doable because the parser and the lexer are already implemented to work in tandem. The other option is to pick a different delimiter syntax to keep backticks available for use in the future. + +If we were to naively compile the expression into a `string.format` call, then implementation details would be observable if you write `` `Your health is {hp}% so you need to heal up.` ``. When lowering the expression, we would need to implicitly insert a `%` character anytime one shows up in a string interpolation token. Otherwise attempting to run this will produce a runtime error where the `%s` token is missing its corresponding string value. + +## Alternatives + +Rather than coming up with a new syntax (which doesn't help issue #5 and #6) and extending `string.format` to accept an extra token, we could just make `%s` call `tostring` and be done. However, doing so would cause programs to be more lenient and the type checker would have no way to infer strings from a `string.format` call. To preserve that, we would need a different token anyway. + +Language | Syntax | Conclusion +----------:|:----------------------|:----------- +Python | `f'Hello {name}'` | Rejected because it's ambiguous with function call syntax. +Swift | `"Hello \(name)"` | Rejected because it changes the meaning of existing strings. +Ruby | `"Hello #{name}"` | Rejected because it changes the meaning of existing strings. +JavaScript | `` `Hello ${name}` `` | Viable option as long as we don't intend to use backticks for other purposes. +C# | `$"Hello {name}"` | Viable option and guarantees no ambiguities with future syntax. + +This leaves us with only two syntax that already exists in other programming languages. The current proposal are for backticks, so the only backward compatible alternative are `$""` literals. We don't necessarily need to use `$` symbol here, but if we were to choose a different symbol, `#` cannot be used. I picked backticks because it doesn't require us to add a stack of closing delimiters in the lexer to make sure each nested string interpolation literals are correctly closed with its opening pair. You only have to count them.