feat(library/tactic): execute Lua tactics using coroutines

This is very important when several Lua tactics are implemented in the
same Lua State object.  In this case, even if we use the par
combinator, a Lua tactic will block the other Lua tactics running in
the same Lua State object.

With this commit, a Lua tactic can use yield to allow other tactics
in the same State object to execute.

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2013-11-28 13:09:30 -08:00
parent 1eeec07713
commit ce674d2d43
6 changed files with 94 additions and 13 deletions

View file

@ -386,20 +386,41 @@ static int mk_lua_tactic01(lua_State * L) {
luaref ref(L, 1);
return push_tactic(L,
mk_tactic01([=](environment const & env, io_state const & ios, proof_state const & s) -> optional<proof_state> {
optional<proof_state> r;
script_state _S(S);
optional<proof_state> r;
luaref coref; // Remark: we have to release the reference in a protected block.
try {
bool done = false;
lua_State * co;
_S.exec_protected([&]() {
co = lua_newthread(L); // create a coroutine for executing user-fun
coref = luaref(L, -1); // make sure co-routine in not deleted
lua_pop(L, 1);
ref.push(); // push user-fun on the stack
push_environment(L, env);
push_environment(L, env); // push args...
push_io_state(L, ios);
push_proof_state(L, s);
pcall(L, 3, 1, 0);
if (is_proof_state(L, -1)) {
r = to_proof_state(L, -1);
lua_xmove(L, co, 4); // move function and arguments to co
done = resume(co, 3);
});
while (!done) {
check_interrupted();
std::this_thread::yield(); // give another thread a chance to execute
_S.exec_protected([&]() {
done = resume(co, 0);
});
}
lua_pop(L, 1);
_S.exec_protected([&]() {
if (is_proof_state(co, -1)) {
r = to_proof_state(co, -1);
}
coref.release();
});
return r;
} catch (...) {
_S.exec_protected([&]() { coref.release(); });
throw;
}
}));
}

View file

@ -115,6 +115,21 @@ void pcall(lua_State * L, int nargs, int nresults, int errorfun) {
check_result(L, result);
}
bool resume(lua_State * L, int nargs) {
#if LUA_VERSION_NUM < 502
int result = lua_resume(L, nargs);
#else
int result = lua_resume(L, nullptr, nargs);
#endif
if (result == LUA_YIELD)
return false;
if (result == 0)
return true;
check_result(L, result);
lean_unreachable();
return true;
}
/**
\brief Wrapper for "customers" that are only using a subset
of Lean libraries.

View file

@ -20,6 +20,11 @@ size_t objlen(lua_State * L, int idx);
void dofile(lua_State * L, char const * fname);
void dostring(lua_State * L, char const * str);
void pcall(lua_State * L, int nargs, int nresults, int errorfun);
/**
\brief Return true iff coroutine is done, false if it has yielded,
and throws an exception if error.
*/
bool resume(lua_State * L, int nargs);
int lessthan(lua_State * L, int idx1, int idx2);
int equal(lua_State * L, int idx1, int idx2);
int get_nonnil_top(lua_State * L);

View file

@ -34,6 +34,13 @@ luaref::~luaref() {
luaL_unref(m_state, LUA_REGISTRYINDEX, m_ref);
}
void luaref::release() {
if (m_state) {
luaL_unref(m_state, LUA_REGISTRYINDEX, m_ref);
m_state = nullptr;
}
}
luaref & luaref::operator=(luaref const & r) {
if (m_ref == r.m_ref)
return *this;

View file

@ -23,6 +23,7 @@ public:
luaref(luaref const & r);
luaref(luaref && r);
~luaref();
void release();
luaref & operator=(luaref const & r);
void push() const;
lua_State * get_state() const { return m_state; }

View file

@ -0,0 +1,32 @@
local env = environment()
local ios = io_state()
local Bool = Const("Bool")
env:add_var("p", Bool)
env:add_var("q", Bool)
local p, q = Consts("p, q")
local ctx = context()
S = State()
-- tactics t1 and t2 uses yield to implement cooperative
-- multitasking
local counter1 = 0
local t1 = tactic(function(env, ios, s)
while true do
counter1 = counter1 + 1
coroutine.yield()
end
end)
local counter2 = 0
local t2 = tactic(function(env, ios, s)
while true do
counter2 = counter2 + 1
coroutine.yield()
end
end)
local T = (t1:par(t2)):try_for(150)
T:solve(env, ios, ctx, p)
print(counter1, counter2)
assert(counter1 > 2)
assert(counter2 > 2)