Raise a jitted cppia exception at the boundary back to native code - #1368
Raise a jitted cppia exception at the boundary back to native code#1368MeguminBOT wants to merge 1 commit into
Conversation
Jitted cppia does not throw. ThrowExpr::genCode writes the value to ctx->exception and jumps to the function epilogue, and jitted code checks the context after every call it makes to other jitted code, so within jitted code the unwind is complete. Nothing did that where jitted code returns to native code. runFunction and runFunctionClosure call compiled(ctx) and never look at ctx->exception, and the compiled branch of CppiaClosure::__run looks only to decide against reading a return value, then answers null() with the exception still set. The caller reads null, and since nothing clears the context, every later call into jitted cppia returns at its first checkException. All three sites now raise it and clear the context, which is the shape TryExpr::runVoid already uses on the interpreted path. The cppia test suite covers it, run with -jit.
|
I think these functions can also be called internally within cppia code, but we want to change only the boundary between the host and cppia code. |
I went and traced it properly. Dropping the What I have locally now is what Before I push it though, I want to check I've got the right line. |
This would be ideal, to avoid introducing unnecessary overhead to the jit. It might not be trivial though. I wonder if @hughsando might have some suggestion on what to do here? |
The problem
With the JIT on, a cppia function that throws does not throw. The call returns
null, and afterthat every call into jitted cppia returns
nullfor the rest of the process, includingfunctions that are perfectly fine.
Why
Jitted cppia does not raise C++ exceptions.
ThrowExpr::genCodewrites the value toctx->exceptionand callsaddThrow, which jumps to the function epilogue. Jitted code then checksctx->exceptionafter every call it makes to other jitted code, so inside jitted code the unwindworks.
The gap is where jitted code returns to native code:
ScriptCallable::runFunctionandScriptCallable::runFunctionClosurecallcompiled(ctx)andreturn without checking
ctx->exceptionat all.CppiaClosure::__rundoes check it, but only to skip reading a returnvalue, and then returns
null()with the exception still sitting there.So the caller gets
nullinstead of an exception. Worse,ctx->exceptionis never cleared, so thenext jitted function to run bails out at its first
checkException.The fix
In
src/hx/cppia/CppiaFunction.cpp, all three sites now raise the exception and clear the context:That is the same thing
TryExpr::runVoidalready does to finish its own unwind on the non-jitpath:
In
runFunctionandrunFunctionClosuretheAutoFrameis scoped so the frame is popped before thethrow.
CppiaClosure::__runalready does this a few hundred lines further down.Test
test/cppiacovers it.ClientThrowerinClient.hxhas one method that throws and one thatreturns a value, and
testThrowReachesTheCallerincases/TestCommon.hxchecks that the throwarrives and that the second call still answers afterwards.
The
-jitmatters.Host.mainalready takes that flag, and without it the non-jit path handlesthe throw correctly and the test passes whether or not the fix is in.
With the fix reverted and everything else applied, the whole suite run with
-jitdoes not completeat all: no utest output, exit 127. The same build without
-jitreportsALL TESTS OK.Reproducing by hand
Script.hx, built withhaxe -m Script --cppia script.cppia:Load it from a host built with
-D scriptable, usingcpp.cppia.Module.fromData(bytes).boot(), then callScript.runand after thatScript.afterthrough reflection, each in a
try/catch.run()after()nullnullboomstill hereboomstill hereThe second column is the reason this matters.
Script.afternever threw and has nothing wrong withit. It returned
nullonly because the context was still holding the earlier exception.Let me know if I got anything wrong!