1) closure
class Closure:
def __init__(self, proto, py_func, n):
self.proto = proto
self.py_func = py_func
self.upvals = []
if proto and len(proto.upvalues) > 0:
self.upvals = [None] * len(proto.upvalues)
elif py_func and n > 0:
self.upvals = [None] * n
2) lua stack
from consts import Consts
class LuaStack:
MAX_STACK_SIZE = 1000
def __init__(self, lua_state):
self.slots = []
self.closure = None
self.varargs = None
self.pc = 0
self.caller = None
self.lua_state = lua_state
self.open_upvalues = {}
# ...
def abs_index(self, idx):
if idx <= Consts.LUA_REGISTRYINDEX:
return idx
return idx if idx >= 0 else idx + len(self.slots) + 1
def is_valid(self, idx):
# upvalues
if idx < Consts.LUA_REGISTRYINDEX:
uvidx = Consts.LUA_REGISTRYINDEX - idx - 1
return self.closure is not None and uvidx < len(self.closure.upvals)
if idx == Consts.LUA_REGISTRYINDEX:
return True
idx = self.abs_index(idx)
return (idx > 0) and (idx <= len(self.slots))
def get(self, idx):
# upvalues
if idx < Consts.LUA_REGISTRYINDEX:
uvidx = Consts.LUA_REGISTRYINDEX - idx - 1
if self.closure and len(self.closure.upvals) > uvidx:
return self.closure.upvals[uvidx]
return None
if idx == Consts.LUA_REGISTRYINDEX:
return self.lua_state.registry
if not self.is_valid(idx):
return None
return self.slots[self.abs_index(idx)-1]
def set(self, idx, val):
# upvalues
if idx < Consts.LUA_REGISTRYINDEX:
uvidx = Consts.LUA_REGISTRYINDEX - idx - 1
if self.closure and len(self.closure.upvals) > uvidx:
self.closure.upvals[uvidx] = val
return
if idx == Consts.LUA_REGISTRYINDEX:
self.lua_state.registry = val
return
if not self.is_valid(idx):
raise Exception('Invalid Index')
self.slots[self.abs_index(idx)-1] = val
# ...
3) lua state
class LuaState:
def __init__(self):
self.stack = LuaStack(self)
self.registry = LuaTable(0, 0)
self.registry.put(Consts.LUA_RIDX_GLOBALS, LuaTable(0, 0))
self.push_lua_stack(LuaStack(Consts.LUA_MIN_STACK))
# ...
def load(self, chunk):
bc = BinaryChunk(chunk)
proto = bc.undump()
closure = Closure(proto, None, 0)
self.stack.push(closure)
if len(proto.upvalues) > 0:
env = self.registry.get(Consts.LUA_RIDX_GLOBALS)
closure.upvals[0] = env
print('env: ', end='')
env.dump()
return ThreadStatus.OK
def load_proto(self, idx):
proto = self.stack.closure.proto.get_protos()[idx]
c = Closure(proto, None, 0)
self.stack.push(c)
for i in range(len(proto.upvalues)):
upvalue = proto.get_upvalues()[i]
idx = upvalue.get_idx()
if upvalue.get_in_stack():
if idx not in self.stack.open_upvalues:
self.stack.open_upvalues[idx] = self.stack.slots[idx]
c.upvals[i] = self.stack.open_upvalues[idx]
else:
c.upvals[i] = self.stack.closure.upvals[idx]
def push_py_function(self, func):
py_closure = Closure(None, func, 0)
self.stack.push(py_closure)
def push_py_closure(self, py_func, n):
closure = Closure(None, py_func, n)
for i in range(n, 0, -1):
v = self.stack.pop()
closure.upvals[i-1] = v
self.stack.push(closure)
def close_upvalues(self, a):
for k, v in self.stack.open_upvalues:
if k >= a-1:
v.migrate()
self.stack.open_upvalues.pop(k)
# ...
4) instruction
def jmp(inst, vm):
a, sbx = inst.a_sbx()
vm.add_pc(sbx)
if a != 0:
vm.close_upvalues(a)
def lua_upvalue_index(idx):
return Consts.LUA_REGISTRYINDEX - idx
def getupval(inst, vm):
a, b, _ = inst.a_b_c()
a += 1
b += 1
vm.copy(lua_upvalue_index(b), a)
def setupval(inst, vm):
a, b, _ = inst.a_b_c()
a += 1
b += 1
vm.copy(a, lua_upvalue_index(b))
def gettabup(inst, vm):
a, b, c = inst.a_b_c()
a += 1
b += 1
vm.get_rk(c)
vm.get_table(lua_upvalue_index(b))
vm.replace(a)
def settabup(inst, vm):
a, b, c = inst.a_b_c()
a += 1
vm.get_rk(b)
vm.get_rk(c)
vm.set_table(lua_upvalue_index(a))
5) test
function newCounter ()
local count = 0
return function () -- 匿名函数
count = count + 1
return count
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
from lua_state import LuaState
def py_print(ls):
nargs = ls.get_top()
for i in range(1, nargs+1):
if ls.is_boolean(i):
print('%t', ls.to_boolean(i), end='')
elif ls.is_string(i):
print(ls.to_string(i), end='')
else:
print(ls.type_name(ls.type(i)), end='')
if i < nargs:
print('\t', end='')
print()
return 0
def main():
with open('./test/closure_upvalue.luac', 'rb') as f:
data = f.read()
ls = LuaState()
ls.register('print', py_print)
ls.load(data)
ls.call(0, 0)
if __name__ == '__main__':
main()
6) result
env: {'print': <closure.Closure object at 0x7f70a7afe780>}
[01] CLOSURE [function][nil]
[02] SETTABUP [function][nil]
[03] GETTABUP [function][nil]
[01] LOADK [0][nil]
[02] CLOSURE [0][function]
[03] RETURN [0][function][function]
[04] CALL [function][nil]
[05] SETTABUP [function][nil]
[06] GETTABUP [function][nil]
[07] GETTABUP [function][function]
[01] GETUPVAL [0][nil]
[02] ADD [1][nil]
[03] SETUPVAL [1][nil]
[04] GETUPVAL [1][nil]
[05] RETURN [1][nil][1]
[08] CALL [function][function][1][2]
1
[09] CALL [function][function]
[10] GETTABUP [function][function]
[11] GETTABUP [function][function]
[01] GETUPVAL [1][nil]
[02] ADD [2][nil]
[03] SETUPVAL [2][nil]
[04] GETUPVAL [2][nil]
[05] RETURN [2][nil][2]
[12] CALL [function][function][2][2]
2
[13] CALL [function][function]
[14] GETTABUP [function][function]
[01] LOADK [0][nil]
[02] CLOSURE [0][function]
[03] RETURN [0][function][function]
[15] CALL [function][function]
[16] SETTABUP [function][function]
[17] GETTABUP [function][function]
[18] GETTABUP [function][function]
[01] GETUPVAL [0][nil]
[02] ADD [1][nil]
[03] SETUPVAL [1][nil]
[04] GETUPVAL [1][nil]
[05] RETURN [1][nil][1]
[19] CALL [function][function][1][2]
1
[20] CALL [function][function]
[21] GETTABUP [function][function]
[22] GETTABUP [function][function]
[01] GETUPVAL [2][nil]
[02] ADD [3][nil]
[03] SETUPVAL [3][nil]
[04] GETUPVAL [3][nil]
[05] RETURN [3][nil][3]
[23] CALL [function][function][3][2]
3
[24] CALL [function][function]
[25] GETTABUP [function][function]
[26] GETTABUP [function][function]
[01] GETUPVAL [1][nil]
[02] ADD [2][nil]
[03] SETUPVAL [2][nil]
[04] GETUPVAL [2][nil]
[05] RETURN [2][nil][2]
[27] CALL [function][function][2][2]
2
[28] CALL [function][function]
[29] RETURN [function][function]
7) 分析
1>
test
function newCounter ()
local count = 0
return function () -- 匿名函数
count = count + 1
return count
end
end
c1 = newCounter()
print(c1()) --> 1
result:
env: {'print': <closure.Closure object at 0x7f2761e8fdd8>}
[01] CLOSURE [function][nil]
[02] SETTABUP [function][nil]
[03] GETTABUP [function][nil]
[01] LOADK [0][nil]
[02] CLOSURE [0][function]
[03] RETURN [0][function][function]
[04] CALL [function][nil]
[05] SETTABUP [function][nil]
[06] GETTABUP [function][nil]
[07] GETTABUP [function][function]
[01] GETUPVAL [0][nil]
[02] ADD [1][nil]
[03] SETUPVAL [1][nil]
[04] GETUPVAL [1][nil]
[05] RETURN [1][nil][1]
[08] CALL [function][function][1][2]
1
[09] CALL [function][function]
[10] RETURN [function][function]
增加调试信息:
set table: print <closure.Closure object at 0x7faebce59a90>
env: {'print': <closure.Closure object at 0x7faebce59a90>}
upvals:
[01] CLOSURE [function][nil]
set table: newCounter <closure.Closure object at 0x7faebce59b00>
[02] SETTABUP [function][nil]
get table: newCounter <closure.Closure object at 0x7faebce59b00>
[03] GETTABUP [function][nil]
[01] LOADK [0][nil]
upvals:
0
[02] CLOSURE [0][function]
[03] RETURN [0][function][function]
[04] CALL [function][nil]
set table: c1 <closure.Closure object at 0x7faebce59f28>
[05] SETTABUP [function][nil]
get table: print <closure.Closure object at 0x7faebce59a90>
[06] GETTABUP [function][nil]
get table: c1 <closure.Closure object at 0x7faebce59f28>
[07] GETTABUP [function][function]
get upval: 0
[01] GETUPVAL [0][nil]
[02] ADD [1][nil]
set upval: 1
[03] SETUPVAL [1][nil]
get upval: 1
[04] GETUPVAL [1][nil]
[05] RETURN [1][nil][1]
[08] CALL [function][function][1][2]
1
[09] CALL [function][function]
[10] RETURN [function][function]
先取到closure c1, 再从c1取upvalue