My point is not that the table has to be implemetned necessarily as a hash with a vector indexed by hash(key) containing pointers/references to keys and collision pointers (or a randomization function like adding some prime modulo N where N is the size of the hash) plus a pointer/refernce to values, and a separate store for keys and for values. There are lot of ways to implement tables including the possibility for some kinds of keys or values to reduce it to a simple integer-indexed vector (i.e. an array).
So the alledged cost of using a table for upvalues is not true: this is here also implementation detail. As well as the way you implement the stack (not necessarily as an array, as it could be as well a double-linked list), so te implementation can also be "stackless" (i.e. the Lua VM engine could be used with a small constant maximum use of the native thread stack, all the rest only needing a head, and even all Lua objects could have a constant maximum native size, allowing efficient reuse of the native heap with low level of fragmentation, and faster use of the native heap, and low footprint on the native heap).
It's important to remember that the Lua table is a generalization of several wellknown structures: lists, arrays, vectors, associative arrays, sets, trees... it can be used in fact for all kinds of collections (including collections of collections). Lua also allows tables to be linked to another table to form a chain of tables (so it also natively allows ternary trees of collections with all orders/ranks).
The concept of "registers" in the Lua.org's implemetnation is nothing else than a conceptualization of an integer-indexed array which is used in hope to improve data locality, it is not necessarily bound to native registers. It is used only because of the way its opcodes are encoded in its VM instruction set (but the same can be said as well in C/C++ stack based implementations using [BP+offset] indexed arrays for local variables, and *caching* a few of them in native registers. The main difference being that "words" used in the Lua.org's implementation is not just n-bit integers (like in native CPUs) but can hold any Lua's datatype (i.e. it is a tagged TV value which can be represented by a fixed-size object, even if some bits are actually references to other "words").