Implement JavaScript exceptions using C++ exceptions
authorSimon Hausmann <simon.hausmann@digia.com>
Fri, 1 Mar 2013 16:04:21 +0000 (17:04 +0100)
committerLars Knoll <lars.knoll@digia.com>
Sun, 3 Mar 2013 19:35:56 +0000 (20:35 +0100)
Instead of registering catch handlers with setjmp and throwing JS exceptions
with longjmp, they are now thrown and caught as C++ exceptions. This allows for
tight interoperability between C++ and JS in the future and allows for clear
semantics with regards to cleaning up memory in the engine when throwing
exceptions. (destructors are guaranteed to be called, unlike with
setjmp/longjmp).

The recent unwind table additions allow for the exceptions to be thrown through
JIT generated code.

Catching the exception is done by re-using the existing IR semantics where the
beginning of a try block is marked by registering an exception handler.
Execution after the registration continues conditionally, based on the return
value of builtin_create_exception_handler. A return value of is 0 the try
block(s) are executed. If an exception is thrown during that time, execution
resumes at the point where builtin_create_exception_handler returns, but with a
return value of 1. If an exception is thrown within the catch handler, the
execution resumes again at the same point, but the inCatch IR variable will
guide execution straight to the finally block(s), which calls
delete_exception_handler.

In the JIT as well as the interpreter this is implemented by entering a C++
code section that contains a C++ try {} catch {} block, in which the calling
function is called again and continues right at the next instruction (or the
interpreter loop is recursively entered). An exception will throw us out of
that scope and back into the try {} catch {} wrapper, which can call again
into the calling function.

The IR guarantees that delete_exception_handler is always called, regardless of
how the try or catch blocks are terminated. That is where in the JIT and
interpreter we return from the nested function call and return back into the
original stack frame, effectively unregistering the catch handler.

Further cleanups with regards to the naming and the exception handler stack
will come in subsequent patches, this is merely the minimal patch set to
change to the new mechanism.

This patch set breaks ARM until ARM exception handler tables are implemented.

The interpreter changes are based on a patchset from Erik
from https://codereview.qt-project.org/#change,45750

Change-Id: I543f2bd37b2186f7e48ffcab177d57b5ce932a0c
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
14 files changed:
src/v4/moth/qv4isel_moth.cpp
src/v4/moth/qv4isel_moth_p.h
src/v4/moth/qv4vme_moth.cpp
src/v4/moth/qv4vme_moth_p.h
src/v4/qmljs_runtime.cpp
src/v4/qmljs_runtime.h
src/v4/qv4codegen.cpp
src/v4/qv4isel_llvm.cpp
src/v4/qv4isel_llvm_p.h
src/v4/qv4isel_masm.cpp
src/v4/qv4isel_masm_p.h
src/v4/qv4isel_p.cpp
src/v4/qv4isel_p.h
tools/v4/main.cpp

index 125014a..d8d7040 100644 (file)
@@ -829,7 +829,7 @@ void InstructionSelection::callBuiltinThrow(IR::Temp *arg)
     addInstruction(call);
 }
 
-void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *)
+void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result)
 {
     Instruction::CallBuiltinCreateExceptionHandler call;
     call.result = getResultParam(result);
index af60ca4..6eaee6d 100644 (file)
@@ -43,7 +43,7 @@ protected:
     virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result);
     virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result);
     virtual void callBuiltinThrow(IR::Temp *arg);
-    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp);
+    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result);
     virtual void callBuiltinDeleteExceptionHandler();
     virtual void callBuiltinGetException(IR::Temp *result);
     virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result);
index ffade96..aea4fce 100644 (file)
@@ -139,7 +139,8 @@ static inline VM::Value *getValueRef(QQmlJS::VM::ExecutionContext *context,
 # define VALUEPTR(param) getValueRef(context, stack, param, stackSize)
 #endif
 
-VM::Value VME::operator()(QQmlJS::VM::ExecutionContext *context, const uchar *code
+VM::Value VME::run(QQmlJS::VM::ExecutionContext *context, const uchar *&code,
+        VM::Value *stack, unsigned stackSize
 #ifdef MOTH_THREADED_INTERPRETER
         , void ***storeJumpTable
 #endif
@@ -161,8 +162,6 @@ VM::Value VME::operator()(QQmlJS::VM::ExecutionContext *context, const uchar *co
     }
 #endif
 
-    VM::Value *stack = 0;
-    unsigned stackSize = 0;
     FunctionState state(context, &code);
 
 #ifdef MOTH_THREADED_INTERPRETER
@@ -260,31 +259,35 @@ VM::Value VME::operator()(QQmlJS::VM::ExecutionContext *context, const uchar *co
     MOTH_END_INSTR(CallBuiltinThrow)
 
     MOTH_BEGIN_INSTR(CallBuiltinCreateExceptionHandler)
-        void *buf = __qmljs_create_exception_handler(context);
-        // The result is the only value we need from the instr to
-        // continue execution when an exception is caught.
+        __qmljs_create_exception_handler(context);
         VM::Value *result = getValueRef(context, stack, instr.result
 #if !defined(QT_NO_DEBUG)
                                         , stackSize
 #endif
                                         );
-        VM::ExecutionContext *oldContext = context;
-        int didThrow = setjmp(* static_cast<jmp_buf *>(buf));
-        context = oldContext;
-        // Two ways to come here: after a create, or after a throw.
-        if (didThrow)
-            // At this point, the interpreter state can be anything but
-            // valid, so first restore the state.
-            restoreState(context, result, code);
-        else
-            // Save the state and any variables we need when catching an
-            // exception, so we can restore the state at that point.
-            saveState(context, result, code);
-        *result = VM::Value::fromInt32(didThrow);
+         try {
+            *result = VM::Value::fromInt32(0);
+            const uchar *tryCode = code;
+            run(context, tryCode, stack, stackSize);
+            code = tryCode;
+        } catch (const VM::Exception &) {
+            try {
+                *result = VM::Value::fromInt32(1);
+                const uchar *catchCode = code;
+                run(context, catchCode, stack, stackSize);
+                code = catchCode;
+            } catch (const VM::Exception &) {
+                *result = VM::Value::fromInt32(1);
+                const uchar *catchCode = code;
+                run(context, catchCode, stack, stackSize);
+                code = catchCode;
+            }
+        }
     MOTH_END_INSTR(CallBuiltinCreateExceptionHandler)
 
     MOTH_BEGIN_INSTR(CallBuiltinDeleteExceptionHandler)
         __qmljs_delete_exception_handler(context);
+        return VM::Value();
     MOTH_END_INSTR(CallBuiltinDeleteExceptionHandler)
 
     MOTH_BEGIN_INSTR(CallBuiltinGetException)
@@ -473,8 +476,8 @@ void **VME::instructionJumpTable()
 {
     static void **jumpTable = 0;
     if (!jumpTable) {
-        VME dummy;
-        dummy(0, 0, &jumpTable);
+        const uchar *code = 0;
+        VME().run(0, code, 0, 0, &jumpTable);
     }
     return jumpTable;
 }
@@ -483,7 +486,7 @@ void **VME::instructionJumpTable()
 VM::Value VME::exec(VM::ExecutionContext *ctxt, const uchar *code)
 {
     VME vme;
-    return vme(ctxt, code);
+    return vme.run(ctxt, code);
 }
 
 void VME::restoreState(VM::ExecutionContext *context, VM::Value *&target, const uchar *&code)
index 2fd877f..0b7fc9b 100644 (file)
@@ -16,17 +16,18 @@ class VME
 public:
     static VM::Value exec(VM::ExecutionContext *, const uchar *);
 
-    VM::Value operator()(QQmlJS::VM::ExecutionContext *, const uchar *code
 #ifdef MOTH_THREADED_INTERPRETER
-            , void ***storeJumpTable = 0
+    static void **instructionJumpTable();
 #endif
-            );
 
+private:
+    VM::Value run(QQmlJS::VM::ExecutionContext *, const uchar *&code,
+            VM::Value *stack = 0, unsigned stackSize = 0
 #ifdef MOTH_THREADED_INTERPRETER
-    static void **instructionJumpTable();
+            , void ***storeJumpTable = 0
 #endif
+            );
 
-private:
     static void restoreState(VM::ExecutionContext *context, VM::Value *&target, const uchar *&code);
     static void saveState(VM::ExecutionContext *context, VM::Value *target, const uchar *code);
 };
index 76361c0..43940e6 100644 (file)
@@ -964,7 +964,7 @@ void __qmljs_throw(ExecutionContext *context, const Value &value)
 
     context->engine->exception = value;
 
-    longjmp(handler.stackFrame, 1);
+    throw Exception();
 }
 
 Q_V4_EXPORT void * __qmljs_create_exception_handler(ExecutionContext *context)
index e649deb..4da9015 100644 (file)
@@ -89,6 +89,9 @@ struct ArrayObject;
 struct ErrorObject;
 struct ExecutionEngine;
 
+struct Exception {
+};
+
 extern "C" {
 
 // context
index 7ad09ca..ad390df 100644 (file)
@@ -2454,10 +2454,7 @@ bool Codegen::visit(TryStatement *ast)
     }
 
     int hasException = _block->newTemp();
-    int contextTemp = _block->newTemp();
-    IR::ExprList *createExceptionArgs = _function->New<IR::ExprList>();
-    createExceptionArgs->init(_block->TEMP(contextTemp));
-    move(_block->TEMP(hasException), _block->CALL(_block->NAME(IR::Name::builtin_create_exception_handler, 0, 0), createExceptionArgs));
+    move(_block->TEMP(hasException), _block->CALL(_block->NAME(IR::Name::builtin_create_exception_handler, 0, 0), 0));
 
     // Pass the hidden "inCatch" and "hasException" TEMPs to the
     // builtin_delete_exception_handler, in order to have those TEMPs alive for
index 5717532..e6010cf 100644 (file)
@@ -427,7 +427,7 @@ void InstructionSelection::callBuiltinThrow(IR::Temp *arg)
     Q_UNREACHABLE();
 }
 
-void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp)
+void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result)
 {
     // TODO
     assert(!"TODO!");
index 43e3dfc..fb28456 100644 (file)
@@ -88,7 +88,7 @@ public: // methods from InstructionSelection:
     virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result);
     virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result);
     virtual void callBuiltinThrow(IR::Temp *arg);
-    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp);
+    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result);
     virtual void callBuiltinDeleteExceptionHandler();
     virtual void callBuiltinGetException(IR::Temp *result);
     virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result);
index 82b0634..1659276 100644 (file)
@@ -123,6 +123,14 @@ void Assembler::addPatch(IR::BasicBlock* targetBlock, Jump targetJump)
     _patches[targetBlock].append(targetJump);
 }
 
+void Assembler::addPatch(DataLabelPtr patch, Label target)
+{
+    DataLabelPatch p;
+    p.dataLabel = patch;
+    p.target = target;
+    _dataLabelPatches.append(p);
+}
+
 Assembler::Pointer Assembler::loadTempAddress(RegisterID reg, IR::Temp *t)
 {
     int32_t offset = 0;
@@ -420,6 +428,9 @@ void Assembler::link(VM::Function *vmFunc)
         functions[ctl.externalFunction.value()] = ctl.functionName;
     }
 
+    foreach (const DataLabelPatch &p, _dataLabelPatches)
+        linkBuffer.patch(p.dataLabel, linkBuffer.locationOf(p.target));
+
     static bool showCode = !qgetenv("SHOW_CODE").isNull();
     if (showCode) {
 #if OS(LINUX)
@@ -630,22 +641,64 @@ void InstructionSelection::callBuiltinThrow(IR::Temp *arg)
     generateFunctionCall(Assembler::Void, __qmljs_builtin_throw, Assembler::ContextRegister, Assembler::Reference(arg));
 }
 
-void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp)
+static void *tryWrapper(ExecutionContext *context, void *localsPtr, void *(*exceptionEntryPointInCallingFunction)(ExecutionContext*, void*, int))
+{
+    void *addressToContinueAt = 0;
+    try {
+        addressToContinueAt = exceptionEntryPointInCallingFunction(context, localsPtr, 0);
+    } catch (const Exception&) {
+        try {
+            addressToContinueAt = exceptionEntryPointInCallingFunction(context, localsPtr, 1);
+        } catch (const Exception&) {
+            addressToContinueAt = exceptionEntryPointInCallingFunction(context, localsPtr, 1);
+        }
+    }
+    return addressToContinueAt;
+}
+
+void InstructionSelection::callBuiltinCreateExceptionHandler(IR::Temp *result)
 {
-    Address contextAddr = _as->loadTempAddress(Assembler::ScratchRegister, contextTemp);
-    _as->storePtr(Assembler::ContextRegister, contextAddr);
-    generateFunctionCall(Assembler::ReturnValueRegister, __qmljs_create_exception_handler, Assembler::ContextRegister);
-    generateFunctionCall(Assembler::ReturnValueRegister, setjmp, Assembler::ReturnValueRegister);
-    _as->loadPtr(contextAddr, Assembler::ContextRegister);
+    generateFunctionCall(Assembler::Void, __qmljs_create_exception_handler, Assembler::ContextRegister);
+
+    // Call tryWrapper, which is going to re-enter the same function again below.
+    // When tryWrapper returns, it returns the with address of where to continue.
+    Assembler::DataLabelPtr movePatch = _as->moveWithPatch(Assembler::TrustedImmPtr(0), Assembler::ScratchRegister);
+    generateFunctionCall(Assembler::ReturnValueRegister, tryWrapper, Assembler::ContextRegister, Assembler::LocalsRegister, Assembler::ScratchRegister);
+    _as->jump(Assembler::ReturnValueRegister);
+
+    // tryWrapper calls us at this place with arg3 == 0 if we're supposed to execute the try block
+    // and arg == 1 if we caught an exception. The generated IR takes care of returning from this
+    // call when deleteExceptionHandler is called.
+    _as->addPatch(movePatch, _as->label());
+    _as->enterStandardStackFrame(/*locals*/0);
+#ifdef ARGUMENTS_IN_REGISTERS
+    _as->move(Assembler::registerForArgument(0), Assembler::ContextRegister);
+    _as->move(Assembler::registerForArgument(1), Assembler::LocalsRegister);
+#else
+    _as->loadPtr(addressForArgument(0), Assembler::ContextRegister);
+    _as->loadPtr(addressForArgument(1), Assembler::LocalsRegister);
+#endif
+
     Address addr = _as->loadTempAddress(Assembler::ScratchRegister, result);
+#ifdef ARGUMENTS_IN_REGISTERS
+    _as->store32(Assembler::registerForArgument(2), addr);
+#else
+    _as->load32(addressForArgument(2), Assembler::ReturnValueRegister);
     _as->store32(Assembler::ReturnValueRegister, addr);
+#endif
     addr.offset += 4;
     _as->store32(Assembler::TrustedImm32(Value::Boolean_Type), addr);
 }
 
 void InstructionSelection::callBuiltinDeleteExceptionHandler()
 {
+    // This assumes that we're in code that was called by tryWrapper, so we return to try wrapper
+    // with the address that we'd like to continue at, which is right after the ret below.
     generateFunctionCall(Assembler::Void, __qmljs_delete_exception_handler, Assembler::ContextRegister);
+    Assembler::DataLabelPtr continuation = _as->moveWithPatch(Assembler::TrustedImmPtr(0), Assembler::ReturnValueRegister);
+    _as->leaveStandardStackFrame(/*locals*/0);
+    _as->ret();
+    _as->addPatch(continuation, _as->label());
 }
 
 void InstructionSelection::callBuiltinGetException(IR::Temp *result)
index 4a12351..d850246 100644 (file)
@@ -216,6 +216,7 @@ public:
     void registerBlock(IR::BasicBlock*);
     void jumpToBlock(IR::BasicBlock* current, IR::BasicBlock *target);
     void addPatch(IR::BasicBlock* targetBlock, Jump targetJump);
+    void addPatch(DataLabelPtr patch, Label target);
 
     Pointer loadTempAddress(RegisterID reg, IR::Temp *t);
 
@@ -723,6 +724,12 @@ private:
     QHash<IR::BasicBlock *, Label> _addrs;
     QHash<IR::BasicBlock *, QVector<Jump> > _patches;
     QList<CallToLink> _callsToLink;
+
+    struct DataLabelPatch {
+        DataLabelPtr dataLabel;
+        Label target;
+    };
+    QList<DataLabelPatch> _dataLabelPatches;
 };
 
 class Q_V4_EXPORT InstructionSelection:
@@ -754,7 +761,7 @@ protected:
     virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result);
     virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result);
     virtual void callBuiltinThrow(IR::Temp *arg);
-    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp);
+    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result);
     virtual void callBuiltinDeleteExceptionHandler();
     virtual void callBuiltinGetException(IR::Temp *result);
     virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result);
index b436015..03fa074 100644 (file)
@@ -311,8 +311,7 @@ void InstructionSelection::callBuiltin(IR::Call *call, IR::Temp *result)
     } return;
 
     case IR::Name::builtin_create_exception_handler: {
-        IR::Temp *arg = call->args->expr->asTemp();
-        callBuiltinCreateExceptionHandler(result, arg);
+        callBuiltinCreateExceptionHandler(result);
         return;
     }
 
index 9c15829..40cf86e 100644 (file)
@@ -103,7 +103,7 @@ public: // to implement by subclasses:
     virtual void callBuiltinPostIncrementName(const QString &name, IR::Temp *result) = 0;
     virtual void callBuiltinPostIncrementValue(IR::Temp *value, IR::Temp *result) = 0;
     virtual void callBuiltinThrow(IR::Temp *arg) = 0;
-    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result, IR::Temp *contextTemp) = 0;
+    virtual void callBuiltinCreateExceptionHandler(IR::Temp *result) = 0;
     virtual void callBuiltinDeleteExceptionHandler() = 0;
     virtual void callBuiltinGetException(IR::Temp *result) = 0;
     virtual void callBuiltinForeachIteratorObject(IR::Temp *arg, IR::Temp *result) = 0;
index f6997eb..d73fa48 100644 (file)
@@ -369,30 +369,30 @@ int main(int argc, char *argv[])
                 const QString code = QString::fromUtf8(file.readAll());
                 file.close();
 
-                void * buf = __qmljs_create_exception_handler(ctx);
-                if (setjmp(*(jmp_buf *)buf)) {
+                __qmljs_create_exception_handler(ctx);
+                try {
+                    QQmlJS::VM::Function *f = QQmlJS::VM::EvalFunction::parseSource(ctx, fn, code, QQmlJS::Codegen::GlobalCode,
+                                                                                    /*strictMode =*/ false, /*inheritContext =*/ false);
+                    if (!f)
+                        continue;
+                    vm.globalCode = f;
+
+                    ctx->strictMode = f->isStrict;
+                    ctx->lookups = f->lookups;
+                    if (debugger)
+                        debugger->aboutToCall(0, ctx);
+                    QQmlJS::VM::Value result = f->code(ctx, f->codeData);
+                    if (debugger)
+                        debugger->justLeft(ctx);
+                    if (!result.isUndefined()) {
+                        if (! qgetenv("SHOW_EXIT_VALUE").isEmpty())
+                            std::cout << "exit value: " << qPrintable(result.toString(ctx)->toQString()) << std::endl;
+                    }
+                } catch (const QQmlJS::VM::Exception&) {
                     showException(ctx);
                     return EXIT_FAILURE;
                 }
 
-                QQmlJS::VM::Function *f = QQmlJS::VM::EvalFunction::parseSource(ctx, fn, code, QQmlJS::Codegen::GlobalCode,
-                                                                                /*strictMode =*/ false, /*inheritContext =*/ false);
-                if (!f)
-                    continue;
-                vm.globalCode = f;
-
-                ctx->strictMode = f->isStrict;
-                ctx->lookups = f->lookups;
-                if (debugger)
-                    debugger->aboutToCall(0, ctx);
-                QQmlJS::VM::Value result = f->code(ctx, f->codeData);
-                if (debugger)
-                    debugger->justLeft(ctx);
-                if (!result.isUndefined()) {
-                    if (! qgetenv("SHOW_EXIT_VALUE").isEmpty())
-                        std::cout << "exit value: " << qPrintable(result.toString(ctx)->toQString()) << std::endl;
-                }
-
             } else {
                 std::cerr << "Error: cannot open file " << fn.toUtf8().constData() << std::endl;
                 return EXIT_FAILURE;