Changeset 275797 in webkit


Ignore:
Timestamp:
Apr 10, 2021 9:12:19 AM (3 years ago)
Author:
mark.lam@apple.com
Message:

Enable VMTraps checks in RETURN_IF_EXCEPTION.
https://bugs.webkit.org/show_bug.cgi?id=224078
rdar://75037057

Reviewed by Keith Miller.

JSTests:

  • stress/watchdog-fire-while-in-forEachInIterable.js: Added.

Source/JavaScriptCore:

In pre-existing code, termination of a VM's execution can already be requested
asynchronously (with respect to the mutator thread). For example, sources of such
a request can be a watchdog timer firing, or a request to stop execution issued
from a main web thread to a worker thread.

This request is made by firing the VMTraps::NeedTermination event on VMTraps.
Firing the event here only means setting a flag to indicate the presence of the
request. We still have to wait till the mutator thread reaches one of the
pre-designated polling check points to call VMTraps::handleTraps() in order to
service the request. As a result of this need to wait for a polling check point,
if the mutator is executing in a long running C++ loop, then a termination request
may not be serviced for a long time.

However, we observed that a lot of our C++ loops already have RETURN_IF_EXCEPTION
checks. Hence, if we can check VMTraps::needHandling() there, we can service the
VMTraps events more frequently even in a lot of C++ loops, and get a better response.

Full details of what this patch changes:

  1. Shorten some type and methods names in the VMTraps class to make code easier to read e.g. EventType => Event, needTrapHandling => needHandling.
  1. Remove the VMTraps::Mask class. Mask was introduced so that we can express a concatenation of multiple VMTraps events to form a bit mask in a simple way. In the end, it isn't flexible enough but makes the code more complicated than necessary. It is now replaced by the simpler solution of using macros to define the Events as bit fields. Having Events as bit fields intrinsically make them easy to concatenate (bitwise or) or filter (bitwise and).

Also removed the unused VMTraps::Error class.

  1. Make VMTraps::BitField a uint32_t. There was always unused padding in VMTraps to allow for this. So, we'll just extend it to a full 32-bit to make it easier to add more events in the future for other uses.
  1. Add NeedExceptionHandling as a VMTrap::Event.
  1. Make VMTraps::m_trapBits Atomic. This makes it easier to set and clear the NeedExceptionHandling bit from the mutator without a lock.
  1. RETURN_IF_EXCEPTION now checks VMTraps::m_trapBits (via VMTraps::needHandling()) instead of checking VM::m_exception. If the VMTraps::m_trapBits is non-null, the macro will call VM:hasExceptionsAfterHandlingTraps() to service VMTraps events as appropriate before returning whether an exception is being thrown. The result of VM:hasExceptionsAfterHandlingTraps() will determine if RETURN_IF_EXCEPTION returns or not.

VM:hasExceptionsAfterHandlingTraps() is intentionally designed to take a minimum
of arguments (just the VM as this pointer). This is because RETURN_IF_EXCEPTION
is called from many places, and we would like to minimize code size bloating
from this change.

  1. Simplify paramaters of VMTraps::handleTraps().

NeedDebuggerBreak's callFrame argument was always vm.topCallFrame anyway.
So, the patch makes it explicit, and removes the callFrame parameter.

NeedWatchdogCheck's globalObject argument should have always been
vm.entryScope->globalObject(), and we can remove the globalObject parameter.

Before this, we pass in whichever globalObject was convenient to grab hold of.
However, the idea of the watchdog is to time out the current script executing
on the stack. Hence, it makes sense to identify thay script by the globalObject
in use at VM entry.

So far, the only clients that uses the watchdog mechanism only operates in
scenarios with only one globalObject anyway. So this formalization to use
VMEntryScope's globalObject does not change the expected behavior.

  1. Make the execution of termination more robust. Before reading this, please read the description of the Events in VMTraps.h first, especially the section on NeedTermination.

Here's the life cycle of a termination:

  1. a client requests termination of the current execution stack by calling VM::notifyNeedTermination(). notifyNeedTermination() does 2 things:
  1. fire the NeedTermination event on VMTraps.
  1. set the VM::m_terminationInProgress flag.
  1. Firing the NeedTermination event on VMTraps means setting the NeedTermination bit on VMTraps::m_trapBits. This bit will be polled by the mutator thread later at various designated points (including RETURN_IF_EXCEPTION, which we added in this patch).

Once the mutator sees the NeedTermination bit is set, it will clear the bit
and throw the TerminationException (see VMTraps::handleTraps()). This is
unless the mutator thread is currently in a DeferTermination scope (see (8)
below). If in a DeferTermination scope, then it will not throw the
TerminationException.

Since the NeedTermination bit is cleared, the VM will no longer call
VMTraps::handleTraps() to service the event. If the mutator thread is in
a DeferTermination scope, then on exiting the scope (at scope destruction),
the scope will see that VM::m_terminationInProgress is set, and throw the
deferred TerminationException then.

  1. The TerminationException will trigger unwinding out of the current stack until we get to the outermost VMEntryScope.
  1. At the the outermost VMEntryScope, we will clear VM::m_terminationInProgress if the NeedTermination bit in VMtraps::m_trapBits is cleared.

If the NeedTermination bit is set, then that means we haven't thrown the
TerminationException yet. Currently, clients expect that we must throw the
TerminationException if NeedTermination was requested (again, read comments
at the top of VMTraps.h).

If the NeedTermination bit is set, we'll leave VM::m_terminationInProgress
set until the next time we re-enter the VM and exit to the outermost
VMEntryScope.

  1. The purpose of VM::m_terminationInProgress is to provide a summary of the fact that the VM is in a state of trying to terminate the current stack.

Note that this state is first indicated by the NeedTermination bit being set
in VMTraps::m_trapBits. Then, in VMTraps::handleTraps(), the state is
handed of with the NeedTermination bit being cleared, and the
TerminationException being thrown.

While the VM is in this termination state, we need to prevent new DFG/FTL
JIT code from being compiled and run. The reason is the firing of the
NeedTermination event has invalidated DFG/FTL code on the stack, thereby
allowing their baseline / LLInt versions which have VMTraps polling checks
to run. We don't want to compile new DFG / FTL code and possibly get stuck
in loops in there before the termination is complete.

In operationOptimize(), we check if VM::m_terminationInProgress is set, and
prevent new DFG (and therefore FTL) code from being compiled if needed.
Note: it is easier to check a single flag, VM::m_terminationInProgress,
then to check both if the NeedTermination bit is set or if the
TerminationException is being being thrown.

  1. One complication of being able to service VMTraps in RETURN_IF_EXCEPTION checks is that some of our code (usually for lengthier initializations and bootstrapping) currently does not handle exceptions well, e.g. JSGlobalObject::init(). They rely on the code crashing if an exception is thrown while still initializing.

However, for a worker thread, a TerminationException (requested by the main
thread) may arrive before the initialization is complete. This can lead to
crashes because part of the initialization may be aborted in the presence of
an exception, while other parts still expect everything prior to have been
initialized correctly. For resource exhaustion cases (which is abnormal), it
is OK to crash. For the TerminationException (which can be part of normal
operation), we should not be crashing.

To work around this, we introduce a DeferTermination RAII scope object that we
deploy in this type of initialization code. With the scope in effect,

  1. if a TerminationException arrives but hasn't been thrown yet, it will be deferred till the scope ends before being thrown.
  2. if a TerminationException has already been thrown, the scope will stash the exception, clear it from the VM so that the initialization code can run to completion, and then re-throw the exception when the scope ends.

Currently, we only need to use the DeferTermination scope in a few places
where we know that initialization code will only run for a short period of time.

DeferTermination should not be used for code that can block waiting on an
external event for a long time. Obviously, doing so will prevent the VM
termination mechanism from working.

  1. Replaced llint_slow_path_check_if_exception_is_uncatchable_and_notify_profiler

and operationCheckIfExceptionIsUncatchableAndNotifyProfiler with
llint_slow_path_retrieve_and_clear_exception_if_catchable and
operationRetrieveAndClearExceptionIfCatchable.

The 2 runtime functions doesn't actually do anything to notify a profiler.
So, we drop that part of the name.

After returning from these runtime functions respectively, the previous LLInt
and JIT code, which calls these runtimes functions, would go on to load
VM::m_exception, and then store a nullptr there to clear it. This is wasteful.

This patch changes the runtime function to clear and return the Exception
instead. As a result, the calling LLInt and JIT code is simplified a bit.

Note also that clearing an exception now also entails clearing the
NeedExceptionHandling bit in VMTraps::m_trapBits in an atomic way. The above
change makes it easy to do this clearing with C++ code.

  1. Fix ScriptFunctionCall::call() to handle exceptions correctly. Previously,

it had one case where it propagates an exception, while another eats it.
Change this function to eat the exception in both cases. This is approproiate
because ScriptFunctionCall is only used to execute some Inspector instrumentation
calls. It doesn't make sense to propagate the exception back to user code.

  1. Fix the lazy initialization of JSGlobalObject::m_defaultCollator to be able to

handle the TerminationException.

  1. Not related to TerminationException, but this patch also fixes

MarkedArgumentBuffer::expandCapacity() to use Gigacage::tryMalloc() instead of
Gigacage::malloc(). This is needed as one of the fixes to make the
accompanying test case work.

This patch increases code size by 320K (144K for JSC, 176K for WebCore) measured
on x86_64.

  • CMakeLists.txt:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • assembler/MacroAssemblerARM64.h:

(JSC::MacroAssemblerARM64::branchTest32):

  • assembler/MacroAssemblerARMv7.h:

(JSC::MacroAssemblerARMv7::branchTest32):

  • assembler/MacroAssemblerMIPS.h:

(JSC::MacroAssemblerMIPS::branchTest32):

  • assembler/MacroAssemblerX86Common.h:

(JSC::MacroAssemblerX86Common::branchTest32):

  • bindings/ScriptFunctionCall.cpp:

(Deprecated::ScriptFunctionCall::call):

  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::compileCheckTraps):

  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::compileCheckTraps):

  • interpreter/Interpreter.cpp:

(JSC::Interpreter::executeProgram):
(JSC::Interpreter::executeCall):
(JSC::Interpreter::executeConstruct):
(JSC::Interpreter::execute):
(JSC::Interpreter::executeModuleProgram):

  • interpreter/InterpreterInlines.h:

(JSC::Interpreter::execute):

  • jit/JITOpcodes.cpp:

(JSC::JIT::emit_op_catch):
(JSC::JIT::emit_op_check_traps):

  • jit/JITOpcodes32_64.cpp:

(JSC::JIT::emit_op_catch):

  • jit/JITOperations.cpp:

(JSC::JSC_DEFINE_JIT_OPERATION):

  • jit/JITOperations.h:
  • llint/LLIntSlowPaths.cpp:

(JSC::LLInt::LLINT_SLOW_PATH_DECL):

  • llint/LLIntSlowPaths.h:
  • llint/LowLevelInterpreter.asm:
  • llint/LowLevelInterpreter32_64.asm:
  • llint/LowLevelInterpreter64.asm:
  • runtime/ArgList.cpp:

(JSC::MarkedArgumentBuffer::expandCapacity):

  • runtime/DeferTermination.h: Added.

(JSC::DeferTermination::DeferTermination):
(JSC::DeferTermination::~DeferTermination):

  • runtime/ExceptionScope.h:

(JSC::ExceptionScope::exception const):
(JSC::ExceptionScope::exception): Deleted.

  • runtime/JSGlobalObject.cpp:

(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::finishCreation):

  • runtime/LazyPropertyInlines.h:

(JSC::ElementType>::callFunc):

  • runtime/StringPrototype.cpp:

(JSC::JSC_DEFINE_HOST_FUNCTION):

  • runtime/VM.cpp:

(JSC::VM::hasExceptionsAfterHandlingTraps):
(JSC::VM::clearException):
(JSC::VM::setException):
(JSC::VM::throwTerminationException):
(JSC::VM::throwException):

  • runtime/VM.h:

(JSC::VM::terminationInProgress const):
(JSC::VM::setTerminationInProgress):
(JSC::VM::notifyNeedTermination):
(JSC::VM::DeferExceptionScope::DeferExceptionScope):
(JSC::VM::DeferExceptionScope::~DeferExceptionScope):
(JSC::VM::handleTraps): Deleted.
(JSC::VM::needTrapHandling): Deleted.
(JSC::VM::needTrapHandlingAddress): Deleted.
(JSC::VM::setException): Deleted.
(JSC::VM::clearException): Deleted.

  • runtime/VMEntryScope.cpp:

(JSC::VMEntryScope::~VMEntryScope):

  • runtime/VMTraps.cpp:

(JSC::VMTraps::tryInstallTrapBreakpoints):
(JSC::VMTraps::fireTrap):
(JSC::VMTraps::handleTraps):
(JSC::VMTraps::takeTopPriorityTrap):
(JSC::VMTraps::deferTermination):
(JSC::VMTraps::undoDeferTermination):

  • runtime/VMTraps.h:

(JSC::VMTraps::onlyContainsAsyncEvents):
(JSC::VMTraps::needHandling const):
(JSC::VMTraps::trapBitsAddress):
(JSC::VMTraps::isDeferringTermination const):
(JSC::VMTraps::notifyGrabAllLocks):
(JSC::VMTraps::hasTrapBit):
(JSC::VMTraps::clearTrapBit):
(JSC::VMTraps::setTrapBit):
(JSC::VMTraps::Mask::Mask): Deleted.
(JSC::VMTraps::Mask::allEventTypes): Deleted.
(JSC::VMTraps::Mask::bits const): Deleted.
(JSC::VMTraps::Mask::init): Deleted.
(JSC::VMTraps::interruptingTraps): Deleted.
(JSC::VMTraps::needTrapHandling): Deleted.
(JSC::VMTraps::needTrapHandlingAddress): Deleted.
(JSC::VMTraps::hasTrapForEvent): Deleted.
(JSC::VMTraps::setTrapForEvent): Deleted.
(JSC::VMTraps::clearTrapForEvent): Deleted.

Source/WebCore:

  1. Add DeferTermination in WorkerOrWorkletScriptController::initScript(). This allows us to avoid having to make all exception checking in WorkerOrWorkletScriptController::initScript() very thorough and complete. Currently, they aren't.
  1. Fix WorkerOrWorkletScriptController::evaluate() to handle the TerminationException.
  1. Fix JSEventListener::handleEvent() to handle the TerminationException correctly. Previously, in one case, it was checking scope.exception() for the exception, but the exception has already been taken out of there.
  • bindings/js/JSEventListener.cpp:

(WebCore::JSEventListener::handleEvent):

  • workers/WorkerOrWorkletScriptController.cpp:

(WebCore::WorkerOrWorkletScriptController::evaluate):
(WebCore::WorkerOrWorkletScriptController::initScript):

Location:
trunk
Files:
2 added
35 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r275653 r275797  
     12021-04-10  Mark Lam  <mark.lam@apple.com>
     2
     3        Enable VMTraps checks in RETURN_IF_EXCEPTION.
     4        https://bugs.webkit.org/show_bug.cgi?id=224078
     5        rdar://75037057
     6
     7        Reviewed by Keith Miller.
     8
     9        * stress/watchdog-fire-while-in-forEachInIterable.js: Added.
     10
    1112021-04-07  Yusuke Suzuki  <ysuzuki@apple.com>
    212
  • trunk/Source/JavaScriptCore/CMakeLists.txt

    r275363 r275797  
    877877    runtime/DateInstance.h
    878878    runtime/DateInstanceCache.h
     879    runtime/DeferTermination.h
    879880    runtime/DeferredWorkTimer.h
    880881    runtime/DefinePropertyAttributes.h
  • trunk/Source/JavaScriptCore/ChangeLog

    r275788 r275797  
     12021-04-10  Mark Lam  <mark.lam@apple.com>
     2
     3        Enable VMTraps checks in RETURN_IF_EXCEPTION.
     4        https://bugs.webkit.org/show_bug.cgi?id=224078
     5        rdar://75037057
     6
     7        Reviewed by Keith Miller.
     8
     9        In pre-existing code, termination of a VM's execution can already be requested
     10        asynchronously (with respect to the mutator thread).  For example, sources of such
     11        a request can be a watchdog timer firing, or a request to stop execution issued
     12        from a main web thread to a worker thread.
     13
     14        This request is made by firing the VMTraps::NeedTermination event on VMTraps.
     15        Firing the event here only means setting a flag to indicate the presence of the
     16        request.  We still have to wait till the mutator thread reaches one of the
     17        pre-designated polling check points to call VMTraps::handleTraps() in order to
     18        service the request.  As a result of this need to wait for a polling check point,
     19        if the mutator is executing in a long running C++ loop, then a termination request
     20        may not be serviced for a long time.
     21
     22        However, we observed that a lot of our C++ loops already have RETURN_IF_EXCEPTION
     23        checks.  Hence, if we can check VMTraps::needHandling() there, we can service the
     24        VMTraps events more frequently even in a lot of C++ loops, and get a better response.
     25
     26        Full details of what this patch changes:
     27
     28        1. Shorten some type and methods names in the VMTraps class to make code easier to
     29           read e.g. EventType => Event, needTrapHandling => needHandling.
     30
     31        2. Remove the VMTraps::Mask class.  Mask was introduced so that we can express a
     32           concatenation of multiple VMTraps events to form a bit mask in a simple way.
     33           In the end, it isn't flexible enough but makes the code more complicated than
     34           necessary.  It is now replaced by the simpler solution of using macros to define
     35           the Events as bit fields.  Having Events as bit fields intrinsically make them
     36           easy to concatenate (bitwise or) or filter (bitwise and).
     37
     38           Also removed the unused VMTraps::Error class.
     39
     40        3. Make VMTraps::BitField a uint32_t.  There was always unused padding in VMTraps
     41           to allow for this.  So, we'll just extend it to a full 32-bit to make it easier
     42           to add more events in the future for other uses.
     43
     44        4. Add NeedExceptionHandling as a VMTrap::Event.
     45
     46        5. Make VMTraps::m_trapBits Atomic.  This makes it easier to set and clear the
     47           NeedExceptionHandling bit from the mutator without a lock.
     48
     49        6. RETURN_IF_EXCEPTION now checks VMTraps::m_trapBits (via VMTraps::needHandling())
     50           instead of checking VM::m_exception.  If the VMTraps::m_trapBits is non-null,
     51           the macro will call VM:hasExceptionsAfterHandlingTraps() to service VMTraps
     52           events as appropriate before returning whether an exception is being thrown.
     53           The result of VM:hasExceptionsAfterHandlingTraps() will determine if
     54           RETURN_IF_EXCEPTION returns or not.
     55
     56           VM:hasExceptionsAfterHandlingTraps() is intentionally designed to take a minimum
     57           of arguments (just the VM as this pointer).  This is because RETURN_IF_EXCEPTION
     58           is called from many places, and we would like to minimize code size bloating
     59           from this change.
     60
     61        7. Simplify paramaters of VMTraps::handleTraps().
     62
     63           NeedDebuggerBreak's callFrame argument was always vm.topCallFrame anyway.
     64           So, the patch makes it explicit, and removes the callFrame parameter.
     65
     66           NeedWatchdogCheck's globalObject argument should have always been
     67           vm.entryScope->globalObject(), and we can remove the globalObject parameter.
     68
     69           Before this, we pass in whichever globalObject was convenient to grab hold of.
     70           However, the idea of the watchdog is to time out the current script executing
     71           on the stack.  Hence, it makes sense to identify thay script by the globalObject
     72           in use at VM entry.
     73
     74           So far, the only clients that uses the watchdog mechanism only operates in
     75           scenarios with only one globalObject anyway.  So this formalization to use
     76           VMEntryScope's globalObject does not change the expected behavior.
     77
     78        8. Make the execution of termination more robust.  Before reading this, please
     79           read the description of the Events in VMTraps.h first, especially the section
     80           on NeedTermination.
     81
     82           Here's the life cycle of a termination:
     83
     84           a. a client requests termination of the current execution stack by calling
     85              VM::notifyNeedTermination().  notifyNeedTermination() does 2 things:
     86
     87               i. fire the NeedTermination event on VMTraps.
     88              ii. set the VM::m_terminationInProgress flag.
     89
     90           b. Firing the NeedTermination event on VMTraps means setting the NeedTermination
     91              bit on VMTraps::m_trapBits.  This bit will be polled by the mutator thread
     92              later at various designated points (including RETURN_IF_EXCEPTION, which we
     93              added in this patch).
     94
     95              Once the mutator sees the NeedTermination bit is set, it will clear the bit
     96              and throw the TerminationException (see VMTraps::handleTraps()).  This is
     97              unless the mutator thread is currently in a DeferTermination scope (see (8)
     98              below).  If in a DeferTermination scope, then it will not throw the
     99              TerminationException.
     100
     101              Since the NeedTermination bit is cleared, the VM will no longer call
     102              VMTraps::handleTraps() to service the event.  If the mutator thread is in
     103              a DeferTermination scope, then on exiting the scope (at scope destruction),
     104              the scope will see that VM::m_terminationInProgress is set, and throw the
     105              deferred TerminationException then.
     106
     107           c. The TerminationException will trigger unwinding out of the current stack
     108              until we get to the outermost VMEntryScope.
     109
     110           d. At the the outermost VMEntryScope, we will clear VM::m_terminationInProgress
     111              if the NeedTermination bit in VMtraps::m_trapBits is cleared.
     112
     113              If the NeedTermination bit is set, then that means we haven't thrown the
     114              TerminationException yet.  Currently, clients expect that we must throw the
     115              TerminationException if NeedTermination was requested (again, read comments
     116              at the top of VMTraps.h).
     117
     118              If the NeedTermination bit is set, we'll leave VM::m_terminationInProgress
     119              set until the next time we re-enter the VM and exit to the outermost
     120              VMEntryScope.
     121
     122           e. The purpose of VM::m_terminationInProgress is to provide a summary of the
     123              fact that the VM is in a state of trying to terminate the current stack.
     124
     125              Note that this state is first indicated by the NeedTermination bit being set
     126              in VMTraps::m_trapBits.  Then, in VMTraps::handleTraps(), the state is
     127              handed of with the NeedTermination bit being cleared, and the
     128              TerminationException being thrown.
     129
     130              While the VM is in this termination state, we need to prevent new DFG/FTL
     131              JIT code from being compiled and run.  The reason is the firing of the
     132              NeedTermination event has invalidated DFG/FTL code on the stack, thereby
     133              allowing their baseline / LLInt versions which have VMTraps polling checks
     134              to run.  We don't want to compile new DFG / FTL code and possibly get stuck
     135              in loops in there before the termination is complete.
     136
     137              In operationOptimize(), we check if VM::m_terminationInProgress is set, and
     138              prevent new DFG (and therefore FTL) code from being compiled if needed.
     139              Note: it is easier to check a single flag, VM::m_terminationInProgress,
     140              then to check both if the NeedTermination bit is set or if the
     141              TerminationException is being being thrown.
     142
     143        9. One complication of being able to service VMTraps in RETURN_IF_EXCEPTION checks
     144           is that some of our code (usually for lengthier initializations and bootstrapping)
     145           currently does not handle exceptions well, e.g. JSGlobalObject::init().  They
     146           rely on the code crashing if an exception is thrown while still initializing.
     147
     148           However, for a worker thread, a TerminationException (requested by the main
     149           thread) may arrive before the initialization is complete.  This can lead to
     150           crashes because part of the initialization may be aborted in the presence of
     151           an exception, while other parts still expect everything prior to have been
     152           initialized correctly.  For resource exhaustion cases (which is abnormal), it
     153           is OK to crash.  For the TerminationException (which can be part of normal
     154           operation), we should not be crashing.
     155
     156           To work around this, we introduce a DeferTermination RAII scope object that we
     157           deploy in this type of initialization code.  With the scope in effect,
     158
     159           a. if a TerminationException arrives but hasn't been thrown yet, it will be
     160              deferred till the scope ends before being thrown.
     161           b. if a TerminationException has already been thrown, the scope will stash
     162              the exception, clear it from the VM so that the initialization code can
     163              run to completion, and then re-throw the exception when the scope ends.
     164
     165           Currently, we only need to use the DeferTermination scope in a few places
     166           where we know that initialization code will only run for a short period of time.
     167
     168           DeferTermination should not be used for code that can block waiting on an
     169           external event for a long time.  Obviously, doing so will prevent the VM
     170           termination mechanism from working.
     171
     172       10. Replaced llint_slow_path_check_if_exception_is_uncatchable_and_notify_profiler
     173           and operationCheckIfExceptionIsUncatchableAndNotifyProfiler with
     174           llint_slow_path_retrieve_and_clear_exception_if_catchable and
     175           operationRetrieveAndClearExceptionIfCatchable.
     176
     177           The 2 runtime functions doesn't actually do anything to notify a profiler.
     178           So, we drop that part of the name.
     179
     180           After returning from these runtime functions respectively, the previous LLInt
     181           and JIT code, which calls these runtimes functions, would go on to load
     182           VM::m_exception, and then store a nullptr there to clear it.  This is wasteful.
     183
     184           This patch changes the runtime function to clear and return the Exception
     185           instead.  As a result, the calling LLInt and JIT code is simplified a bit.
     186
     187           Note also that clearing an exception now also entails clearing the
     188           NeedExceptionHandling bit in VMTraps::m_trapBits in an atomic way.  The above
     189           change makes it easy to do this clearing with C++ code.
     190
     191       11. Fix ScriptFunctionCall::call() to handle exceptions correctly.  Previously,
     192           it had one case where it propagates an exception, while another eats it.
     193           Change this function to eat the exception in both cases.  This is approproiate
     194           because ScriptFunctionCall is only used to execute some Inspector instrumentation
     195           calls.  It doesn't make sense to propagate the exception back to user code.
     196
     197       12. Fix the lazy initialization of JSGlobalObject::m_defaultCollator to be able to
     198           handle the TerminationException.
     199
     200       13. Not related to TerminationException, but this patch also fixes
     201           MarkedArgumentBuffer::expandCapacity() to use Gigacage::tryMalloc() instead of
     202           Gigacage::malloc().  This is needed as one of the fixes to make the
     203           accompanying test case work.
     204
     205        This patch increases code size by 320K (144K for JSC, 176K for WebCore) measured
     206        on x86_64.
     207
     208        * CMakeLists.txt:
     209        * JavaScriptCore.xcodeproj/project.pbxproj:
     210        * assembler/MacroAssemblerARM64.h:
     211        (JSC::MacroAssemblerARM64::branchTest32):
     212        * assembler/MacroAssemblerARMv7.h:
     213        (JSC::MacroAssemblerARMv7::branchTest32):
     214        * assembler/MacroAssemblerMIPS.h:
     215        (JSC::MacroAssemblerMIPS::branchTest32):
     216        * assembler/MacroAssemblerX86Common.h:
     217        (JSC::MacroAssemblerX86Common::branchTest32):
     218        * bindings/ScriptFunctionCall.cpp:
     219        (Deprecated::ScriptFunctionCall::call):
     220        * dfg/DFGSpeculativeJIT.cpp:
     221        (JSC::DFG::SpeculativeJIT::compileCheckTraps):
     222        * ftl/FTLLowerDFGToB3.cpp:
     223        (JSC::FTL::DFG::LowerDFGToB3::compileCheckTraps):
     224        * interpreter/Interpreter.cpp:
     225        (JSC::Interpreter::executeProgram):
     226        (JSC::Interpreter::executeCall):
     227        (JSC::Interpreter::executeConstruct):
     228        (JSC::Interpreter::execute):
     229        (JSC::Interpreter::executeModuleProgram):
     230        * interpreter/InterpreterInlines.h:
     231        (JSC::Interpreter::execute):
     232        * jit/JITOpcodes.cpp:
     233        (JSC::JIT::emit_op_catch):
     234        (JSC::JIT::emit_op_check_traps):
     235        * jit/JITOpcodes32_64.cpp:
     236        (JSC::JIT::emit_op_catch):
     237        * jit/JITOperations.cpp:
     238        (JSC::JSC_DEFINE_JIT_OPERATION):
     239        * jit/JITOperations.h:
     240        * llint/LLIntSlowPaths.cpp:
     241        (JSC::LLInt::LLINT_SLOW_PATH_DECL):
     242        * llint/LLIntSlowPaths.h:
     243        * llint/LowLevelInterpreter.asm:
     244        * llint/LowLevelInterpreter32_64.asm:
     245        * llint/LowLevelInterpreter64.asm:
     246        * runtime/ArgList.cpp:
     247        (JSC::MarkedArgumentBuffer::expandCapacity):
     248        * runtime/DeferTermination.h: Added.
     249        (JSC::DeferTermination::DeferTermination):
     250        (JSC::DeferTermination::~DeferTermination):
     251        * runtime/ExceptionScope.h:
     252        (JSC::ExceptionScope::exception const):
     253        (JSC::ExceptionScope::exception): Deleted.
     254        * runtime/JSGlobalObject.cpp:
     255        (JSC::JSGlobalObject::init):
     256        (JSC::JSGlobalObject::finishCreation):
     257        * runtime/LazyPropertyInlines.h:
     258        (JSC::ElementType>::callFunc):
     259        * runtime/StringPrototype.cpp:
     260        (JSC::JSC_DEFINE_HOST_FUNCTION):
     261        * runtime/VM.cpp:
     262        (JSC::VM::hasExceptionsAfterHandlingTraps):
     263        (JSC::VM::clearException):
     264        (JSC::VM::setException):
     265        (JSC::VM::throwTerminationException):
     266        (JSC::VM::throwException):
     267        * runtime/VM.h:
     268        (JSC::VM::terminationInProgress const):
     269        (JSC::VM::setTerminationInProgress):
     270        (JSC::VM::notifyNeedTermination):
     271        (JSC::VM::DeferExceptionScope::DeferExceptionScope):
     272        (JSC::VM::DeferExceptionScope::~DeferExceptionScope):
     273        (JSC::VM::handleTraps): Deleted.
     274        (JSC::VM::needTrapHandling): Deleted.
     275        (JSC::VM::needTrapHandlingAddress): Deleted.
     276        (JSC::VM::setException): Deleted.
     277        (JSC::VM::clearException): Deleted.
     278        * runtime/VMEntryScope.cpp:
     279        (JSC::VMEntryScope::~VMEntryScope):
     280        * runtime/VMTraps.cpp:
     281        (JSC::VMTraps::tryInstallTrapBreakpoints):
     282        (JSC::VMTraps::fireTrap):
     283        (JSC::VMTraps::handleTraps):
     284        (JSC::VMTraps::takeTopPriorityTrap):
     285        (JSC::VMTraps::deferTermination):
     286        (JSC::VMTraps::undoDeferTermination):
     287        * runtime/VMTraps.h:
     288        (JSC::VMTraps::onlyContainsAsyncEvents):
     289        (JSC::VMTraps::needHandling const):
     290        (JSC::VMTraps::trapBitsAddress):
     291        (JSC::VMTraps::isDeferringTermination const):
     292        (JSC::VMTraps::notifyGrabAllLocks):
     293        (JSC::VMTraps::hasTrapBit):
     294        (JSC::VMTraps::clearTrapBit):
     295        (JSC::VMTraps::setTrapBit):
     296        (JSC::VMTraps::Mask::Mask): Deleted.
     297        (JSC::VMTraps::Mask::allEventTypes): Deleted.
     298        (JSC::VMTraps::Mask::bits const): Deleted.
     299        (JSC::VMTraps::Mask::init): Deleted.
     300        (JSC::VMTraps::interruptingTraps): Deleted.
     301        (JSC::VMTraps::needTrapHandling): Deleted.
     302        (JSC::VMTraps::needTrapHandlingAddress): Deleted.
     303        (JSC::VMTraps::hasTrapForEvent): Deleted.
     304        (JSC::VMTraps::setTrapForEvent): Deleted.
     305        (JSC::VMTraps::clearTrapForEvent): Deleted.
     306
    13072021-04-09  Alexey Shvayka  <shvaikalesh@gmail.com>
    2308
  • trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

    r275363 r275797  
    19931993                FE99B2491C24C3D300C82159 /* JITNegGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = FE99B2481C24B6D300C82159 /* JITNegGenerator.h */; };
    19941994                FE9F3FB92613C7890069E89F /* ResourceExhaustion.h in Headers */ = {isa = PBXBuildFile; fileRef = FE9F3FB82613C7880069E89F /* ResourceExhaustion.h */; };
     1995                FE9F3FC826163CA90069E89F /* DeferTermination.h in Headers */ = {isa = PBXBuildFile; fileRef = FE9F3FC626163CA90069E89F /* DeferTermination.h */; settings = {ATTRIBUTES = (Private, ); }; };
    19951996                FEA08620182B7A0400F6D851 /* Breakpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = FEA0861E182B7A0400F6D851 /* Breakpoint.h */; settings = {ATTRIBUTES = (Private, ); }; };
    19961997                FEA08621182B7A0400F6D851 /* DebuggerPrimitives.h in Headers */ = {isa = PBXBuildFile; fileRef = FEA0861F182B7A0400F6D851 /* DebuggerPrimitives.h */; settings = {ATTRIBUTES = (Private, ); }; };
     
    53645365                FE9F3FB82613C7880069E89F /* ResourceExhaustion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResourceExhaustion.h; sourceTree = "<group>"; };
    53655366                FE9F3FBA2613C87C0069E89F /* ResourceExhaustion.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ResourceExhaustion.cpp; sourceTree = "<group>"; };
     5367                FE9F3FC626163CA90069E89F /* DeferTermination.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeferTermination.h; sourceTree = "<group>"; };
    53665368                FEA0861E182B7A0400F6D851 /* Breakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpoint.h; sourceTree = "<group>"; };
    53675369                FEA0861F182B7A0400F6D851 /* DebuggerPrimitives.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebuggerPrimitives.h; sourceTree = "<group>"; };
     
    72877289                                BCD203470E17135E002C7E82 /* DatePrototype.cpp */,
    72887290                                BCD203480E17135E002C7E82 /* DatePrototype.h */,
     7291                                FE9F3FC626163CA90069E89F /* DeferTermination.h */,
    72897292                                534638761E71E06E00F12AC1 /* DeferredWorkTimer.cpp */,
    72907293                                534638741E70DDEC00F12AC1 /* DeferredWorkTimer.h */,
     
    96649667                                0FC20CB61852E2C600C9E954 /* DFGStrengthReductionPhase.h in Headers */,
    96659668                                0F63947815DCE34B006A597C /* DFGStructureAbstractValue.h in Headers */,
     9669                                FE9F3FC826163CA90069E89F /* DeferTermination.h in Headers */,
    96669670                                0F50AF3C193E8B3900674EE8 /* DFGStructureClobberState.h in Headers */,
    96679671                                0F2FCCFF18A60070001A27F8 /* DFGThreadData.h in Headers */,
  • trunk/Source/JavaScriptCore/assembler/MacroAssemblerARM64.h

    r274401 r275797  
    11/*
    2  * Copyright (C) 2012-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2012-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    29432943    }
    29442944
     2945    Jump branchTest32(ResultCondition cond, AbsoluteAddress address, TrustedImm32 mask = TrustedImm32(-1))
     2946    {
     2947        move(TrustedImmPtr(address.m_ptr), getCachedMemoryTempRegisterIDAndInvalidate());
     2948        load32(Address(memoryTempRegister), memoryTempRegister);
     2949        return branchTest32(cond, memoryTempRegister, mask);
     2950    }
     2951
    29452952    Jump branchTest64(ResultCondition cond, RegisterID reg, RegisterID mask)
    29462953    {
  • trunk/Source/JavaScriptCore/assembler/MacroAssemblerARMv7.h

    r269349 r275797  
    11/*
    2  * Copyright (C) 2009-2019 Apple Inc. All rights reserved.
     2 * Copyright (C) 2009-2021 Apple Inc. All rights reserved.
    33 * Copyright (C) 2010 University of Szeged
    44 *
     
    16901690    }
    16911691
     1692    Jump branchTest32(ResultCondition cond, AbsoluteAddress address, TrustedImm32 mask = TrustedImm32(-1))
     1693    {
     1694        // use addressTempRegister incase the branchTest32 we call uses dataTempRegister. :-/
     1695        move(TrustedImmPtr(address.m_ptr), addressTempRegister);
     1696        load32(Address(addressTempRegister), addressTempRegister);
     1697        return branchTest32(cond, addressTempRegister, mask);
     1698    }
     1699
    16921700    Jump branchTest8(ResultCondition cond, BaseIndex address, TrustedImm32 mask = TrustedImm32(-1))
    16931701    {
  • trunk/Source/JavaScriptCore/assembler/MacroAssemblerMIPS.h

    r269349 r275797  
    11/*
    2  * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
     2 * Copyright (C) 2008-2021 Apple Inc. All rights reserved.
    33 * Copyright (C) 2010 MIPS Technologies, Inc. All rights reserved.
    44 *
     
    19621962    {
    19631963        load32(address, dataTempRegister);
     1964        return branchTest32(cond, dataTempRegister, mask);
     1965    }
     1966
     1967    Jump branchTest32(ResultCondition cond, AbsoluteAddress address, TrustedImm32 mask = TrustedImm32(-1))
     1968    {
     1969        load32(address.m_ptr, dataTempRegister);
    19641970        return branchTest32(cond, dataTempRegister, mask);
    19651971    }
  • trunk/Source/JavaScriptCore/assembler/MacroAssemblerX86Common.h

    r270208 r275797  
    11/*
    2  * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
     2 * Copyright (C) 2008-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    27122712            m_assembler.testl_i32m(mask.m_value, address.offset, address.base, address.index, address.scale);
    27132713        return Jump(m_assembler.jCC(x86Condition(cond)));
     2714    }
     2715   
     2716    Jump branchTest32(ResultCondition cond, AbsoluteAddress address, TrustedImm32 mask = TrustedImm32(-1))
     2717    {
     2718        move(TrustedImmPtr(address.m_ptr), scratchRegister());
     2719        return branchTest32(cond, Address(scratchRegister()), mask);
    27142720    }
    27152721   
  • trunk/Source/JavaScriptCore/bindings/ScriptFunctionCall.cpp

    r275648 r275797  
    109109    VM& vm = m_globalObject->vm();
    110110    JSLockHolder lock(vm);
    111     auto scope = DECLARE_THROW_SCOPE(vm);
     111    auto scope = DECLARE_CATCH_SCOPE(vm);
     112
     113    auto makeExceptionResult = [&] (Exception* exception) -> Expected<JSValue, NakedPtr<Exception>> {
     114        // Do not treat a terminated execution exception as having an exception. Just treat it as an empty result.
     115        if (!vm.isTerminationException(exception))
     116            return makeUnexpected(exception);
     117        return { };
     118    };
    112119
    113120    JSValue function = thisObject->get(m_globalObject, Identifier::fromString(vm, m_name));
    114     if (UNLIKELY(scope.exception()))
    115         return makeUnexpected(scope.exception());
     121    Exception* exception = scope.exception();
     122    if (UNLIKELY(exception)) {
     123        scope.clearException();
     124        return makeExceptionResult(exception);
     125    }
    116126
    117127    auto callData = getCallData(vm, function);
     
    120130
    121131    JSValue result;
    122     NakedPtr<Exception> exception;
     132    NakedPtr<Exception> uncaughtException;
    123133    if (m_callHandler)
    124         result = m_callHandler(m_globalObject, function, callData, thisObject, m_arguments, exception);
     134        result = m_callHandler(m_globalObject, function, callData, thisObject, m_arguments, uncaughtException);
    125135    else
    126         result = JSC::call(m_globalObject, function, callData, thisObject, m_arguments, exception);
     136        result = JSC::call(m_globalObject, function, callData, thisObject, m_arguments, uncaughtException);
    127137
    128     if (exception) {
    129         // Do not treat a terminated execution exception as having an exception. Just treat it as an empty result.
    130         if (!vm.isTerminationException(exception))
    131             return makeUnexpected(exception);
    132         return { };
    133     }
     138    if (uncaughtException)
     139        return makeExceptionResult(uncaughtException);
    134140
    135141    return result;
  • trunk/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

    r275597 r275797  
    11/*
    2  * Copyright (C) 2011-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2011-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    22232223    GPRReg unusedGPR = unused.gpr();
    22242224
    2225     JITCompiler::Jump needTrapHandling = m_jit.branchTest8(JITCompiler::NonZero,
    2226         JITCompiler::AbsoluteAddress(m_jit.vm().needTrapHandlingAddress()));
     2225    JITCompiler::Jump needTrapHandling = m_jit.branchTest32(JITCompiler::NonZero,
     2226        JITCompiler::AbsoluteAddress(m_jit.vm().traps().trapBitsAddress()),
     2227        TrustedImm32(VMTraps::AsyncEvents));
    22272228
    22282229    addSlowPathGenerator(slowPathCall(needTrapHandling, this, operationHandleTraps, unusedGPR, TrustedImmPtr::weakPointer(m_graph, m_graph.globalObjectFor(node->origin.semantic))));
  • trunk/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

    r275650 r275797  
    11/*
    2  * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    1340113401        LBasicBlock continuation = m_out.newBlock();
    1340213402       
    13403         LValue state = m_out.load8ZeroExt32(m_out.absolute(vm().needTrapHandlingAddress()));
    13404         m_out.branch(m_out.isZero32(state),
     13403        LValue trapBits = m_out.load32(m_out.absolute(vm().traps().trapBitsAddress()));
     13404        m_out.branch(m_out.testIsZero32(trapBits, m_out.constInt32(VMTraps::AsyncEvents)),
    1340513405            usually(continuation), rarely(needTrapHandling));
    1340613406
  • trunk/Source/JavaScriptCore/interpreter/Interpreter.cpp

    r275648 r275797  
    802802        return checkedReturn(throwException(globalObject, throwScope, error));
    803803
    804     constexpr auto trapsMask = VMTraps::interruptingTraps();
    805     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    806         vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
    807         RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
     804    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     805        if (vm.hasExceptionsAfterHandlingTraps())
     806            return throwScope.exception();
    808807    }
    809808
     
    869868        return checkedReturn(throwStackOverflowError(globalObject, throwScope));
    870869
    871     constexpr auto trapsMask = VMTraps::interruptingTraps();
    872     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    873         vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
    874         RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
     870    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     871        if (vm.hasExceptionsAfterHandlingTraps())
     872            return throwScope.exception();
    875873    }
    876874
     
    948946    }
    949947
    950     constexpr auto trapsMask = VMTraps::interruptingTraps();
    951     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    952         vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
    953         RETURN_IF_EXCEPTION(throwScope, nullptr);
     948    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     949        if (vm.hasExceptionsAfterHandlingTraps())
     950            return nullptr;
    954951    }
    955952
     
    10631060    }
    10641061
    1065     constexpr auto trapsMask = VMTraps::interruptingTraps();
    1066     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    1067         vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
    1068         RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
     1062    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     1063        if (vm.hasExceptionsAfterHandlingTraps())
     1064            return throwScope.exception();
    10691065    }
    10701066
     
    12211217        return checkedReturn(throwStackOverflowError(globalObject, throwScope));
    12221218
    1223     constexpr auto trapsMask = VMTraps::interruptingTraps();
    1224     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    1225         vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
    1226         RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
     1219    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     1220        if (vm.hasExceptionsAfterHandlingTraps())
     1221            return throwScope.exception();
    12271222    }
    12281223
  • trunk/Source/JavaScriptCore/interpreter/InterpreterInlines.h

    r269511 r275797  
    11/*
    22 * Copyright (C) 2016 Yusuke Suzuki <utatane.tea@gmail.com>
    3  * Copyright (C) 2016-2020 Apple Inc. All rights reserved.
     3 * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
    44 *
    55 * Redistribution and use in source and binary forms, with or without
     
    8181    StackStats::CheckPoint stackCheckPoint;
    8282
    83     constexpr auto trapsMask = VMTraps::interruptingTraps();
    84     if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
    85         vm.handleTraps(closure.protoCallFrame->globalObject, closure.oldCallFrame, trapsMask);
    86         RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
     83    if (UNLIKELY(vm.traps().needHandling(VMTraps::NonDebuggerAsyncEvents))) {
     84        ASSERT(vm.topCallFrame == closure.oldCallFrame);
     85        if (vm.hasExceptionsAfterHandlingTraps())
     86            return throwScope.exception();
    8787    }
    8888
  • trunk/Source/JavaScriptCore/jit/JITOpcodes.cpp

    r271489 r275797  
    843843    addPtr(TrustedImm32(stackPointerOffsetFor(codeBlock()) * sizeof(Register)), callFrameRegister, stackPointerRegister);
    844844
    845     callOperationNoExceptionCheck(operationCheckIfExceptionIsUncatchableAndNotifyProfiler, TrustedImmPtr(&vm()));
    846     Jump isCatchableException = branchTest32(Zero, returnValueGPR);
     845    callOperationNoExceptionCheck(operationRetrieveAndClearExceptionIfCatchable, TrustedImmPtr(&vm()));
     846    Jump isCatchableException = branchTest32(NonZero, returnValueGPR);
    847847    jumpToExceptionHandler(vm());
    848848    isCatchableException.link(this);
    849849
    850     move(TrustedImmPtr(m_vm), regT3);
    851     load64(Address(regT3, VM::exceptionOffset()), regT0);
    852     store64(TrustedImm64(JSValue::encode(JSValue())), Address(regT3, VM::exceptionOffset()));
     850    move(returnValueGPR, regT0);
    853851    emitPutVirtualRegister(bytecode.m_exception);
    854852
     
    12191217void JIT::emit_op_check_traps(const Instruction*)
    12201218{
    1221     addSlowCase(branchTest8(NonZero, AbsoluteAddress(m_vm->needTrapHandlingAddress())));
     1219    addSlowCase(branchTest32(NonZero, AbsoluteAddress(m_vm->traps().trapBitsAddress()), TrustedImm32(VMTraps::AsyncEvents)));
    12221220}
    12231221
  • trunk/Source/JavaScriptCore/jit/JITOpcodes32_64.cpp

    r271279 r275797  
    932932    addPtr(TrustedImm32(stackPointerOffsetFor(codeBlock()) * sizeof(Register)), callFrameRegister, stackPointerRegister);
    933933
    934     callOperationNoExceptionCheck(operationCheckIfExceptionIsUncatchableAndNotifyProfiler, TrustedImmPtr(&vm()));
    935     Jump isCatchableException = branchTest32(Zero, returnValueGPR);
     934    callOperationNoExceptionCheck(operationRetrieveAndClearExceptionIfCatchable, TrustedImmPtr(&vm()));
     935    Jump isCatchableException = branchTest32(NonZero, returnValueGPR);
    936936    jumpToExceptionHandler(vm());
    937937    isCatchableException.link(this);
    938938
    939     move(TrustedImmPtr(m_vm), regT3);
    940 
    941939    // Now store the exception returned by operationThrow.
    942     load32(Address(regT3, VM::exceptionOffset()), regT2);
     940    move(returnValueGPR, regT2);
    943941    move(TrustedImm32(JSValue::CellTag), regT1);
    944 
    945     store32(TrustedImm32(0), Address(regT3, VM::exceptionOffset()));
    946942
    947943    emitStore(bytecode.m_exception, regT1, regT2);
  • trunk/Source/JavaScriptCore/jit/JITOperations.cpp

    r275648 r275797  
    17661766    CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
    17671767    JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
    1768     ASSERT(vm.needTrapHandling());
    1769     vm.handleTraps(globalObject, callFrame);
     1768    ASSERT(vm.traps().needHandling(VMTraps::AsyncEvents));
     1769    vm.traps().handleTraps(VMTraps::AsyncEvents);
    17701770    return nullptr;
    17711771}
     
    18421842    }
    18431843   
     1844    if (UNLIKELY(vm.terminationInProgress())) {
     1845        // If termination of the current stack of execution is in progress,
     1846        // then we need to hold off on optimized compiles so that termination
     1847        // checks will be called, and we can unwind out of the current stack.
     1848        CODEBLOCK_LOG_EVENT(codeBlock, "delayOptimizeToDFG", ("Terminating current execution"));
     1849        updateAllPredictionsAndOptimizeAfterWarmUp(codeBlock);
     1850        return encodeResult(nullptr, nullptr);
     1851    }
     1852
    18441853    Debugger* debugger = codeBlock->globalObject()->debugger();
    18451854    if (UNLIKELY(debugger && (debugger->isStepping() || codeBlock->baselineAlternative()->hasDebuggerRequests()))) {
     
    35123521}
    35133522
    3514 JSC_DEFINE_JIT_OPERATION(operationCheckIfExceptionIsUncatchableAndNotifyProfiler, int32_t, (VM* vmPointer))
     3523JSC_DEFINE_JIT_OPERATION(operationRetrieveAndClearExceptionIfCatchable, JSCell*, (VM* vmPointer))
    35153524{
    35163525    VM& vm = *vmPointer;
     
    35203529    RELEASE_ASSERT(!!scope.exception());
    35213530
    3522     if (vm.isTerminationException(scope.exception())) {
     3531    Exception* exception = scope.exception();
     3532    if (vm.isTerminationException(exception)) {
    35233533        genericUnwind(vm, callFrame);
    3524         return 1;
    3525     }
    3526     return 0;
     3534        return nullptr;
     3535    }
     3536
     3537    // We want to clear the exception here rather than in the catch prologue
     3538    // JIT code because clearing it also entails clearing a bit in an Atomic
     3539    // bit field in VMTraps.
     3540    scope.clearException();
     3541    return exception;
    35273542}
    35283543
  • trunk/Source/JavaScriptCore/jit/JITOperations.h

    r272580 r275797  
    11/*
    2  * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    296296JSC_DECLARE_JIT_OPERATION(operationExceptionFuzzWithCallFrame, void, (VM*));
    297297
    298 JSC_DECLARE_JIT_OPERATION(operationCheckIfExceptionIsUncatchableAndNotifyProfiler, int32_t, (VM*));
     298JSC_DECLARE_JIT_OPERATION(operationRetrieveAndClearExceptionIfCatchable, JSCell*, (VM*));
    299299JSC_DECLARE_JIT_OPERATION(operationInstanceOfCustom, size_t, (JSGlobalObject*, EncodedJSValue encodedValue, JSObject* constructor, EncodedJSValue encodedHasInstance));
    300300
  • trunk/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp

    r275648 r275797  
    20822082{
    20832083    LLINT_BEGIN_NO_SET_PC();
    2084     ASSERT(vm.needTrapHandling());
    2085     vm.handleTraps(globalObject, callFrame);
     2084    ASSERT(vm.traps().needHandling(VMTraps::AsyncEvents));
     2085    vm.traps().handleTraps(VMTraps::AsyncEvents);
    20862086    UNUSED_PARAM(pc);
    20872087    LLINT_RETURN_TWO(throwScope.exception(), globalObject);
     
    21832183}
    21842184
    2185 LLINT_SLOW_PATH_DECL(slow_path_check_if_exception_is_uncatchable_and_notify_profiler)
     2185LLINT_SLOW_PATH_DECL(slow_path_retrieve_and_clear_exception_if_catchable)
    21862186{
    21872187    LLINT_BEGIN();
     
    21892189    RELEASE_ASSERT(!!throwScope.exception());
    21902190
    2191     if (vm.isTerminationException(throwScope.exception()))
    2192         LLINT_RETURN_TWO(pc, bitwise_cast<void*>(static_cast<uintptr_t>(1)));
    2193     LLINT_RETURN_TWO(pc, nullptr);
     2191    Exception* exception = throwScope.exception();
     2192    if (vm.isTerminationException(exception))
     2193        LLINT_RETURN_TWO(pc, nullptr);
     2194
     2195    // We want to clear the exception here rather than in the catch prologue
     2196    // JIT code because clearing it also entails clearing a bit in an Atomic
     2197    // bit field in VMTraps.
     2198    throwScope.clearException();
     2199    LLINT_RETURN_TWO(pc, exception);
    21942200}
    21952201
  • trunk/Source/JavaScriptCore/llint/LLIntSlowPaths.h

    r272580 r275797  
    11/*
    2  * Copyright (C) 2011-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2011-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    138138LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_get_from_scope);
    139139LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_put_to_scope);
    140 LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_check_if_exception_is_uncatchable_and_notify_profiler);
     140LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_retrieve_and_clear_exception_if_catchable);
    141141LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_profile_catch);
    142142LLINT_SLOW_PATH_HIDDEN_DECL(slow_path_log_shadow_chicken_prologue);
  • trunk/Source/JavaScriptCore/llint/LowLevelInterpreter.asm

    r275626 r275797  
    1 # Copyright (C) 2011-2020 Apple Inc. All rights reserved.
     1# Copyright (C) 2011-2021 Apple Inc. All rights reserved.
    22#
    33# Redistribution and use in source and binary forms, with or without
     
    266266const NoPtrTag = constexpr NoPtrTag
    267267 
     268# VMTraps data
     269const VMTrapsAsyncEvents = constexpr VMTraps::AsyncEvents
    268270
    269271# Some register conventions.
     
    21942196    loadp CodeBlock[cfr], t1
    21952197    loadp CodeBlock::m_vm[t1], t1
    2196     loadb VM::m_traps+VMTraps::m_needTrapHandling[t1], t0
     2198    loadi VM::m_traps+VMTraps::m_trapBits[t1], t0
     2199    andi VMTrapsAsyncEvents, t0
    21972200    btpnz t0, .handleTraps
    21982201.afterHandlingTraps:
  • trunk/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm

    r275626 r275797  
    1 # Copyright (C) 2011-2020 Apple Inc. All rights reserved.
     1# Copyright (C) 2011-2021 Apple Inc. All rights reserved.
    22#
    33# Redistribution and use in source and binary forms, with or without
     
    22162216    subp PB, PC
    22172217
    2218     callSlowPath(_llint_slow_path_check_if_exception_is_uncatchable_and_notify_profiler)
    2219     bpeq r1, 0, .isCatchableException
     2218    callSlowPath(_llint_slow_path_retrieve_and_clear_exception_if_catchable)
     2219    bpneq r1, 0, .isCatchableException
    22202220    jmp _llint_throw_from_slow_path_trampoline
    22212221
    22222222.isCatchableException:
    2223     loadp CodeBlock[cfr], t3
    2224     loadp CodeBlock::m_vm[t3], t3
    2225 
    2226     loadp VM::m_exception[t3], t0
    2227     storep 0, VM::m_exception[t3]
     2223    move r1, t0
    22282224    get(size, OpCatch, m_exception, t2)
    22292225    storei t0, PayloadOffset[cfr, t2, 8]
  • trunk/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm

    r275626 r275797  
    1 # Copyright (C) 2011-2020 Apple Inc. All rights reserved.
     1# Copyright (C) 2011-2021 Apple Inc. All rights reserved.
    22#
    33# Redistribution and use in source and binary forms, with or without
     
    23672367    subp PB, PC
    23682368
    2369     callSlowPath(_llint_slow_path_check_if_exception_is_uncatchable_and_notify_profiler)
    2370     bpeq r1, 0, .isCatchableException
     2369    callSlowPath(_llint_slow_path_retrieve_and_clear_exception_if_catchable)
     2370    bpneq r1, 0, .isCatchableException
    23712371    jmp _llint_throw_from_slow_path_trampoline
    23722372
    23732373.isCatchableException:
    2374     loadp CodeBlock[cfr], t3
    2375     loadp CodeBlock::m_vm[t3], t3
    2376 
    2377     loadp VM::m_exception[t3], t0
    2378     storep 0, VM::m_exception[t3]
     2374    move r1, t0
    23792375    get(size, OpCatch, m_exception, t2)
    23802376    storeq t0, [cfr, t2, 8]
  • trunk/Source/JavaScriptCore/runtime/ArgList.cpp

    r273138 r275797  
    9191    if (UNLIKELY(checkedSize.hasOverflowed()))
    9292        return this->overflowed();
    93     EncodedJSValue* newBuffer = static_cast<EncodedJSValue*>(Gigacage::malloc(Gigacage::JSValue, checkedSize.unsafeGet()));
     93    EncodedJSValue* newBuffer = static_cast<EncodedJSValue*>(Gigacage::tryMalloc(Gigacage::JSValue, checkedSize.unsafeGet()));
     94    if (!newBuffer)
     95        return this->overflowed();
    9496    for (int i = 0; i < m_size; ++i) {
    9597        newBuffer[i] = m_buffer[i];
  • trunk/Source/JavaScriptCore/runtime/ExceptionScope.h

    r245646 r275797  
    11/*
    2  * Copyright (C) 2016-2018 Apple Inc. All rights reserved.
     2 * Copyright (C) 2016-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    5151    VM& vm() const { return m_vm; }
    5252    unsigned recursionDepth() const { return m_recursionDepth; }
    53     Exception* exception() { return m_vm.exception(); }
     53    Exception* exception() const { return m_vm.exception(); }
    5454
    5555    ALWAYS_INLINE void assertNoException() { RELEASE_ASSERT_WITH_MESSAGE(!exception(), "%s", unexpectedExceptionMessage().data()); }
     
    8585public:
    8686    ALWAYS_INLINE VM& vm() const { return m_vm; }
    87     ALWAYS_INLINE Exception* exception() { return m_vm.exception(); }
     87    ALWAYS_INLINE Exception* exception() const { return m_vm.exception(); }
    8888
    8989    ALWAYS_INLINE void assertNoException() { ASSERT(!exception()); }
     
    105105
    106106#define RETURN_IF_EXCEPTION(scope__, value__) do { \
    107         if (UNLIKELY((scope__).exception())) \
    108             return value__; \
     107        JSC::VM& vm = (scope__).vm(); \
     108        ASSERT(!!(scope__).exception() == vm.traps().needHandling(JSC::VMTraps::NeedExceptionHandling)); \
     109        if (UNLIKELY(vm.traps().needHandling(JSC::VMTraps::NonDebuggerEvents))) { \
     110            if (vm.hasExceptionsAfterHandlingTraps()) \
     111                return value__; \
     112        } \
    109113    } while (false)
    110114
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObject.cpp

    r275439 r275797  
    6060#include "Debugger.h"
    6161#include "DebuggerScope.h"
     62#include "DeferTermination.h"
    6263#include "DirectArguments.h"
    6364#include "ErrorConstructor.h"
     
    11101111            JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(init.owner);
    11111112            VM& vm = init.vm;
    1112             auto scope = DECLARE_CATCH_SCOPE(vm);
     1113            auto scope = DECLARE_THROW_SCOPE(vm);
    11131114            IntlCollator* collator = IntlCollator::create(vm, globalObject->collatorStructure());
    11141115            collator->initializeCollator(globalObject, jsUndefined(), jsUndefined());
    1115             scope.releaseAssertNoException();
     1116            RETURN_IF_EXCEPTION(scope, void());
    11161117            init.set(collator);
    11171118        });
     
    24042405void JSGlobalObject::finishCreation(VM& vm)
    24052406{
     2407    DeferTermination deferTermination(vm);
    24062408    Base::finishCreation(vm);
    24072409    structure(vm)->setGlobalObject(vm, this);
     
    24142416void JSGlobalObject::finishCreation(VM& vm, JSObject* thisValue)
    24152417{
     2418    DeferTermination deferTermination(vm);
    24162419    Base::finishCreation(vm);
    24172420    structure(vm)->setGlobalObject(vm, this);
  • trunk/Source/JavaScriptCore/runtime/LazyPropertyInlines.h

    r273138 r275797  
    9898    initializer.property.m_pointer |= initializingTag;
    9999    callStatelessLambda<void, Func>(initializer);
     100    if (UNLIKELY(initializer.property.m_pointer & initializingTag)) {
     101        VM& vm = initializer.vm;
     102        Exception* exception = vm.exceptionForInspection();
     103        RELEASE_ASSERT(exception && vm.isTerminationException(exception));
     104        RELEASE_ASSERT(initializer.property.m_pointer & lazyTag);
     105        return nullptr;
     106    }
    100107    RELEASE_ASSERT(!(initializer.property.m_pointer & lazyTag));
    101     RELEASE_ASSERT(!(initializer.property.m_pointer & initializingTag));
    102108    return bitwise_cast<ElementType*>(initializer.property.m_pointer);
    103109}
  • trunk/Source/JavaScriptCore/runtime/StringPrototype.cpp

    r272471 r275797  
    11/*
    22 *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
    3  *  Copyright (C) 2004-2020 Apple Inc. All rights reserved.
     3 *  Copyright (C) 2004-2021 Apple Inc. All rights reserved.
    44 *  Copyright (C) 2009 Torch Mobile, Inc.
    55 *  Copyright (C) 2015 Jordan Harband (ljharb@gmail.com)
     
    15401540        collator = IntlCollator::create(vm, globalObject->collatorStructure());
    15411541        collator->initializeCollator(globalObject, locales, options);
    1542         RETURN_IF_EXCEPTION(scope, encodedJSValue());
    1543     }
     1542    }
     1543    RETURN_IF_EXCEPTION(scope, encodedJSValue());
    15441544    RELEASE_AND_RETURN(scope, JSValue::encode(collator->compareStrings(globalObject, string, that)));
    15451545}
  • trunk/Source/JavaScriptCore/runtime/VM.cpp

    r275648 r275797  
    11/*
    2  * Copyright (C) 2008-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2008-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    959959}
    960960
     961bool VM::hasExceptionsAfterHandlingTraps()
     962{
     963    if (UNLIKELY(traps().needHandling(VMTraps::NonDebuggerAsyncEvents)))
     964        m_traps.handleTraps(VMTraps::NonDebuggerAsyncEvents);
     965    return exception();
     966}
     967
     968void VM::clearException()
     969{
     970#if ENABLE(EXCEPTION_SCOPE_VERIFICATION)
     971    m_needExceptionCheck = false;
     972    m_nativeStackTraceOfLastThrow = nullptr;
     973    m_throwingThread = nullptr;
     974#endif
     975    m_exception = nullptr;
     976    traps().clearTrapBit(VMTraps::NeedExceptionHandling);
     977}
     978
     979void VM::setException(Exception* exception)
     980{
     981    m_exception = exception;
     982    m_lastException = exception;
     983    if (exception)
     984        traps().setTrapBit(VMTraps::NeedExceptionHandling);
     985}
     986
    961987void VM::throwTerminationException()
    962988{
     989    ASSERT(!m_traps.isDeferringTermination());
    963990    setException(terminationException());
    964991}
     
    966993Exception* VM::throwException(JSGlobalObject* globalObject, Exception* exceptionToThrow)
    967994{
     995    // The TerminationException should never be overridden.
     996    if (m_exception && isTerminationException(m_exception))
     997        return m_exception;
     998
    968999    // The TerminationException is not like ordinary exceptions that should be
    9691000    // reported to the debugger. The fact that the TerminationException uses the
  • trunk/Source/JavaScriptCore/runtime/VM.h

    r275648 r275797  
    338338    WeakRandom& random() { return m_random; }
    339339    Integrity::Random& integrityRandom() { return m_integrityRandom; }
     340
     341    bool terminationInProgress() const { return m_terminationInProgress; }
     342    void setTerminationInProgress(bool value) { m_terminationInProgress = value; }
    340343
    341344    JS_EXPORT_PRIVATE Exception* ensureTerminationException();
     
    10861089    VMTraps& traps() { return m_traps; }
    10871090
    1088     void handleTraps(JSGlobalObject* globalObject, CallFrame* callFrame, VMTraps::Mask mask = VMTraps::Mask::allEventTypes()) { m_traps.handleTraps(globalObject, callFrame, mask); }
    1089 
    1090     bool needTrapHandling() { return m_traps.needTrapHandling(); }
    1091     bool needTrapHandling(VMTraps::Mask mask) { return m_traps.needTrapHandling(mask); }
    1092     void* needTrapHandlingAddress() { return m_traps.needTrapHandlingAddress(); }
     1091    JS_EXPORT_PRIVATE bool hasExceptionsAfterHandlingTraps();
    10931092
    10941093    // These may be called concurrently from another thread.
    10951094    void notifyNeedDebuggerBreak() { m_traps.fireTrap(VMTraps::NeedDebuggerBreak); }
    10961095    void notifyNeedShellTimeoutCheck() { m_traps.fireTrap(VMTraps::NeedShellTimeoutCheck); }
    1097     void notifyNeedTermination() { m_traps.fireTrap(VMTraps::NeedTermination); }
     1096    void notifyNeedTermination()
     1097    {
     1098        setTerminationInProgress(true);
     1099        m_traps.fireTrap(VMTraps::NeedTermination);
     1100    }
    10981101    void notifyNeedWatchdogCheck() { m_traps.fireTrap(VMTraps::NeedWatchdogCheck); }
    10991102
     
    11131116    public:
    11141117        DeferExceptionScope(VM& vm)
    1115             : m_savedException(vm.m_exception, nullptr)
     1118            : m_vm(vm)
     1119            , m_exceptionWasSet(vm.m_exception)
     1120            , m_savedException(vm.m_exception, nullptr)
    11161121            , m_savedLastException(vm.m_lastException, nullptr)
    11171122        {
     1123            if (m_exceptionWasSet)
     1124                m_vm.traps().clearTrapBit(VMTraps::NeedExceptionHandling);
    11181125        }
    11191126
     1127        ~DeferExceptionScope()
     1128        {
     1129            if (m_exceptionWasSet)
     1130                m_vm.traps().setTrapBit(VMTraps::NeedExceptionHandling);
     1131        }
     1132
    11201133    private:
     1134        VM& m_vm;
     1135        bool m_exceptionWasSet;
    11211136        SetForScope<Exception*> m_savedException;
    11221137        SetForScope<Exception*> m_savedLastException;
     
    11461161    }
    11471162
    1148     void setException(Exception* exception)
    1149     {
    1150         m_exception = exception;
    1151         m_lastException = exception;
    1152     }
    11531163    Exception* exception() const
    11541164    {
     
    11581168        return m_exception;
    11591169    }
    1160     void clearException()
    1161     {
    1162 #if ENABLE(EXCEPTION_SCOPE_VERIFICATION)
    1163         m_needExceptionCheck = false;
    1164         m_nativeStackTraceOfLastThrow = nullptr;
    1165         m_throwingThread = nullptr;
    1166 #endif
    1167         m_exception = nullptr;
    1168     }
     1170
     1171    JS_EXPORT_PRIVATE void clearException();
     1172    JS_EXPORT_PRIVATE void setException(Exception*);
    11691173
    11701174#if ENABLE(C_LOOP)
     
    12551259    uintptr_t m_currentWeakRefVersion { 0 };
    12561260
     1261    bool m_terminationInProgress { false };
     1262
    12571263    Lock m_loopHintExecutionCountLock;
    12581264    HashMap<const Instruction*, std::pair<unsigned, std::unique_ptr<uint64_t>>> m_loopHintExecutionCounts;
  • trunk/Source/JavaScriptCore/runtime/VMEntryScope.cpp

    r270861 r275797  
    11/*
    2  * Copyright (C) 2013-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    101101        listener();
    102102
     103    // If the trap bit is still set at this point, then it means that VMTraps::handleTraps()
     104    // has not yet been called for this termination request. As a result, we've not thrown a
     105    // TerminationException yet. Some client code relies on detecting the presence of the
     106    // TerminationException in order to signal that a termination was requested. As a result,
     107    // we want to stay in the TerminationInProgress state until VMTraps::handleTraps() (which
     108    // clears the trap bit) gets called, and the TerminationException gets thrown.
     109    //
     110    // Note: perhaps there's a better way for the client to know that a termination was
     111    // requested (after all, the request came from the client). However, this is how the
     112    // client code currently works. Changing that will take some significant effort to hunt
     113    // down all the places in client code that currently rely on this behavior.
     114    if (!m_vm.traps().needHandling(VMTraps::NeedTermination))
     115        m_vm.setTerminationInProgress(false);
    103116    m_vm.clearScratchBuffers();
    104117}
  • trunk/Source/JavaScriptCore/runtime/VMTraps.cpp

    r275648 r275797  
    146146            return; // Let the SignalSender try again later.
    147147
    148         if (!needTrapHandling()) {
     148        if (!needHandling(VMTraps::AsyncEvents)) {
    149149            // Too late. Someone else already handled the trap.
    150150            return;
     
    253253            return PollResult::Stop;
    254254
    255         if (!traps().needTrapHandling())
     255        if (!traps().needHandling(VMTraps::AsyncEvents))
    256256            return PollResult::Wait;
    257257
     
    323323}
    324324
    325 void VMTraps::fireTrap(VMTraps::EventType eventType)
     325void VMTraps::fireTrap(VMTraps::Event event)
    326326{
    327327    ASSERT(!vm().currentThreadIsHoldingAPILock());
     328    ASSERT(onlyContainsAsyncEvents(event));
    328329    {
    329330        auto locker = holdLock(*m_lock);
    330331        ASSERT(!m_isShuttingDown);
    331         setTrapForEvent(locker, eventType);
     332        setTrapBit(event);
    332333        m_needToInvalidatedCodeBlocks = true;
    333334    }
     
    346347}
    347348
    348 void VMTraps::handleTraps(JSGlobalObject* globalObject, CallFrame* callFrame, VMTraps::Mask mask)
     349void VMTraps::handleTraps(VMTraps::BitField mask)
    349350{
    350351    VM& vm = this->vm();
    351352    auto scope = DECLARE_THROW_SCOPE(vm);
     353    ASSERT(onlyContainsAsyncEvents(mask));
     354    ASSERT(needHandling(mask));
     355
     356    if (isDeferringTermination())
     357        mask &= ~NeedTermination;
    352358
    353359    {
     
    360366    }
    361367
    362     ASSERT(needTrapHandling(mask));
    363     while (needTrapHandling(mask)) {
    364         auto eventType = takeTopPriorityTrap(mask);
    365         switch (eventType) {
     368    while (needHandling(mask)) {
     369        auto event = takeTopPriorityTrap(mask);
     370        switch (event) {
    366371        case NeedDebuggerBreak:
    367372            dataLog("VM ", RawPointer(&vm), " on pid ", getCurrentProcessID(), " received NeedDebuggerBreak trap\n");
    368             invalidateCodeBlocksOnStack(callFrame);
     373            invalidateCodeBlocksOnStack(vm.topCallFrame);
    369374            break;
    370375
     
    376381        case NeedWatchdogCheck:
    377382            ASSERT(vm.watchdog());
    378             if (LIKELY(!vm.watchdog()->shouldTerminate(globalObject)))
     383            ASSERT(vm.entryScope->globalObject());
     384            if (LIKELY(!vm.watchdog()->shouldTerminate(vm.entryScope->globalObject())))
    379385                continue;
     386            vm.setTerminationInProgress(true);
    380387            FALLTHROUGH;
    381388
    382389        case NeedTermination:
    383             RELEASE_AND_RETURN(scope, vm.throwTerminationException());
    384 
     390            ASSERT(vm.terminationInProgress());
     391            scope.release();
     392            if (!isDeferringTermination())
     393                vm.throwTerminationException();
     394            return;
     395
     396        case NeedExceptionHandling:
    385397        default:
    386398            RELEASE_ASSERT_NOT_REACHED();
     
    389401}
    390402
    391 auto VMTraps::takeTopPriorityTrap(VMTraps::Mask mask) -> EventType
     403auto VMTraps::takeTopPriorityTrap(VMTraps::BitField mask) -> Event
    392404{
    393405    auto locker = holdLock(*m_lock);
    394     for (int i = 0; i < NumberOfEventTypes; ++i) {
    395         EventType eventType = static_cast<EventType>(i);
    396         if (hasTrapForEvent(locker, eventType, mask)) {
    397             clearTrapForEvent(locker, eventType);
    398             return eventType;
    399         }
    400     }
    401     return Invalid;
     406
     407    // Note: the EventBitShift is already sorted in highest to lowest priority
     408    // i.e. a bit shift of 0 is highest priority, etc.
     409    for (int i = 0; i < NumberOfEvents; ++i) {
     410        Event event = static_cast<Event>(1 << i);
     411        if (hasTrapBit(event, mask)) {
     412            clearTrapBit(event);
     413            return event;
     414        }
     415    }
     416    return NoEvent;
     417}
     418
     419void VMTraps::deferTermination()
     420{
     421    auto locker = holdLock(*m_lock);
     422    m_deferTerminationCount++;
     423    ASSERT(m_deferTerminationCount < UINT_MAX);
     424
     425    VM& vm = this->vm();
     426    Exception* pendingException = vm.exception();
     427    if (pendingException && vm.isTerminationException(pendingException)) {
     428        vm.clearException();
     429        m_suspendedTerminationException = true;
     430    }
     431}
     432
     433void VMTraps::undoDeferTermination()
     434{
     435    auto locker = holdLock(*m_lock);
     436    ASSERT(m_deferTerminationCount > 0);
     437    if (--m_deferTerminationCount == 0) {
     438        VM& vm = this->vm();
     439        if (m_suspendedTerminationException || vm.terminationInProgress())
     440            vm.setException(vm.terminationException());
     441    }
    402442}
    403443
  • trunk/Source/JavaScriptCore/runtime/VMTraps.h

    r261538 r275797  
    11/*
    2  * Copyright (C) 2017-2020 Apple Inc. All rights reserved.
     2 * Copyright (C) 2017-2021 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    4242
    4343class VMTraps {
    44     typedef uint8_t BitField;
    4544public:
    46     enum class Error {
    47         None,
    48         LockUnavailable,
    49         NotJITCode
     45    using BitField = uint32_t;
     46    static constexpr size_t bitsInBitField = sizeof(BitField) * CHAR_BIT;
     47
     48    // The following are the type of VMTrap events / signals that can be fired.
     49    // This list should be sorted in servicing priority order from highest to
     50    // lowest.
     51    //
     52    // The currently imlemented events are (in highest to lowest priority):
     53    //
     54    //  NeedShellTimeoutCheck
     55    //  - Only used by the jsc shell to check if we need to force a hard shutdown.
     56    //  - This event may fire more than once before the jsc shell forces the
     57    //    shutdown (see NeedWatchdogCheck's discussion of CPU time for why
     58    //    this may be).
     59    //
     60    //  NeedTermination
     61    //  - Used to request the termination of execution of the "current" stack.
     62    //    Note: "Termination" here simply means we terminate whatever is currently
     63    //    executing on the stack. It does not mean termination of the VM, and hence,
     64    //    is not permanent. Permanent VM termination mechanisms (like stopping the
     65    //    request to stop a woker thread) may use this Event to terminate the
     66    //    "current" stack, but it needs to do some additional work to prevent
     67    //    re-entry into the VM.
     68    //
     69    //  - The mechanism for achieving this stack termination is by throwing the
     70    //    uncatchable TerminationException that piggy back on the VM's exception
     71    //    handling machinery to the unwind stack. The TerminationException is
     72    //    uncatchable in the sense that the VM will refuse to let JS code's
     73    //    catch handlers catch the exception. C++ code in the VM (that calls into
     74    //    JS) needs to do exception checks, and make sure to propagate the
     75    //    exception if it is the TerminationException.
     76    //
     77    //  - Again, the termination request is not permanent. Once the VM unwinds out
     78    //    of the "current" execution state on the stack, the client may choose to
     79    //    clear the exception, and re-enter the VM to executing JS code again.
     80    //    See NeedWatchdogCheck below on why the VM watchdog needs this ability
     81    //    to re-enter the VM after terminating the current stack.
     82    //
     83    //  - Many clients enter the VM via APIs that return an uncaught exception
     84    //    in a NakedPointer<Exception>&. Those APIs would automatically clear
     85    //    the uncaught TerminationException and return it via the
     86    //    NakedPointer<Exception>&. Hence, the VM is ready for re-entry upon
     87    //    returning to the client.
     88    //
     89    //  - In the above notes, "current" (as in "current" stack) is in quotes because
     90    //    NeedTermination needs to guarantee that the TerminationException has
     91    //    been thrown in response to this event. If the event fires just before
     92    //    the VM exits and the TerminationException was not thrown yet, then we'll
     93    //    keep the NeedTermination trap bit set for the next VM entry. In this case,
     94    //    the termination will actual happen on the next stack of execution.
     95    //
     96    //    This behavior is needed because some clients rely on seeing an uncaught
     97    //    TerminationException to know that a termination has been requested.
     98    //    Technically, there are better ways for the client to know about the
     99    //    termination request (after all, the termination is initiated by the
     100    //    client). However, this is how some current client code works. So, we need
     101    //    to retain this behavior until we can change all the clients that rely on
     102    //    it.
     103    //
     104    //  NeedWatchdogCheck
     105    //  - Used to request a check as to whether the watchdog timer has expired.
     106    //    Note: the watchdog timeout is logically measured in CPU time. However,
     107    //    the real timer implementation (that fires this NeedWatchdogCheck event)
     108    //    has to operate on wall clock time. Hence, NeedWatchdogCheck firing does not
     109    //    necessarily mean that the watchdog timeout has expired, and we can expect
     110    //    to see NeedWatchdogCheck firing more than once for a single watchdog
     111    //    timeout.
     112    //
     113    //  - The watchdog mechanism has the option to request termination of the
     114    //    the current execution stack on watchdog timeout (see
     115    //    Watchdog::shouldTerminate()). If termination is requested, it will
     116    //    be executed via the same mechanism as NeedTermination (see how the
     117    //    NeedWatchdogCheck case can fall through to the NeedTermination case in
     118    //    VMTraps::handleTraps()).
     119    //
     120    //  - The watchdog timing out is not permanent i.e. after terminating the
     121    //    current stack, the client may choose to re-enter the VM to execute more
     122    //    JS. For example, a client may use the watchdog to ensure that an untrusted
     123    //    3rd party script (that it runs) does not get trapped in an infinite loop.
     124    //    If so, the watchdog timeout can terminate that script. After terminating
     125    //    that bad script, the client may choose to allow other 3rd party scripts
     126    //    to execute, or even allow more tries on the current one that timed out.
     127    //    Hence, the timeout and termination must not be permanent.
     128    //
     129    //    This is why termination via the NeedTermination event is not permanent,
     130    //    but only terminates the "current" stack.
     131    //
     132    //  NeedDebuggerBreak
     133    //  - Services asynchronous debugger break requests.
     134    //
     135    //  NeedExceptionHandling
     136    //  - Unlike the other events (which are asynchronous to the mutator thread),
     137    //    NeedExceptionHandling is set when the mutator thread throws a JS exception
     138    //    and cleared when the exception is handled / caught.
     139    //
     140    //  - The reason why NeedExceptionHandling is a bit on VMTraps as well is so
     141    //    that we can piggy back on all the RETURN_IF_EXCEPTION checks in C++ code
     142    //    to service VMTraps as well. Having the NeedExceptionHandling event as
     143    //    part of VMTraps allows RETURN_IF_EXCEPTION to optimally only do a single
     144    //    check to determine if the VM possibly has a pending exception to handle,
     145    //    as well as if there are asynchronous VMTraps events to handle.
     146
     147#define FOR_EACH_VMTRAPS_EVENTS(v) \
     148    v(NeedShellTimeoutCheck) \
     149    v(NeedTermination) \
     150    v(NeedWatchdogCheck) \
     151    v(NeedDebuggerBreak) \
     152    v(NeedExceptionHandling)
     153
     154#define DECLARE_VMTRAPS_EVENT_BIT_SHIFT(event__)  event__##BitShift,
     155    enum EventBitShift {
     156        FOR_EACH_VMTRAPS_EVENTS(DECLARE_VMTRAPS_EVENT_BIT_SHIFT)
     157        NumberOfEvents, // This entry must be last in this list.
    50158    };
    51 
    52     enum EventType {
    53         // Sorted in servicing priority order from highest to lowest.
    54         NeedDebuggerBreak,
    55         NeedShellTimeoutCheck,
    56         NeedTermination,
    57         NeedWatchdogCheck,
    58         NumberOfEventTypes, // This entry must be last in this list.
    59         Invalid
    60     };
    61 
    62     class Mask {
    63     public:
    64         enum AllEventTypes { AllEventTypesTag };
    65         constexpr Mask(AllEventTypes)
    66             : m_mask(std::numeric_limits<BitField>::max())
    67         { }
    68         static constexpr Mask allEventTypes() { return Mask(AllEventTypesTag); }
    69 
    70         constexpr Mask(const Mask&) = default;
    71         constexpr Mask(Mask&&) = default;
    72 
    73         template<typename... Arguments>
    74         constexpr Mask(Arguments... args)
    75             : m_mask(0)
    76         {
    77             init(args...);
    78         }
    79 
    80         BitField bits() const { return m_mask; }
    81 
    82     private:
    83         template<typename... Arguments>
    84         constexpr void init(EventType eventType, Arguments... args)
    85         {
    86             ASSERT(eventType < NumberOfEventTypes);
    87             m_mask |= (1 << eventType);
    88             init(args...);
    89         }
    90 
    91         constexpr void init() { }
    92 
    93         BitField m_mask;
    94     };
    95 
    96     static constexpr Mask interruptingTraps() { return Mask(NeedShellTimeoutCheck, NeedTermination, NeedWatchdogCheck); }
     159#undef DECLARE_VMTRAPS_EVENT_BIT_SHIFT
     160
     161    using Event = BitField;
     162
     163#define DECLARE_VMTRAPS_EVENT(event__) \
     164    static_assert(event__##BitShift < bitsInBitField); \
     165    static constexpr Event event__ = (1 << event__##BitShift);
     166    FOR_EACH_VMTRAPS_EVENTS(DECLARE_VMTRAPS_EVENT)
     167#undef DECLARE_VMTRAPS_EVENT
     168
     169#undef FOR_EACH_VMTRAPS_EVENTS
     170
     171    static constexpr Event NoEvent = 0;
     172
     173    static_assert(NumberOfEvents <= bitsInBitField);
     174    static constexpr BitField AllEvents = (1ull << NumberOfEvents) - 1;
     175    static constexpr BitField AsyncEvents = AllEvents & ~NeedExceptionHandling;
     176    static constexpr BitField NonDebuggerEvents = AllEvents & ~NeedDebuggerBreak;
     177    static constexpr BitField NonDebuggerAsyncEvents = AsyncEvents & ~NeedDebuggerBreak;
     178
     179    static constexpr bool onlyContainsAsyncEvents(BitField events)
     180    {
     181        return (AsyncEvents & events) && !(~AsyncEvents & events);
     182    }
    97183
    98184    ~VMTraps();
     
    103189    void willDestroyVM();
    104190
    105     bool needTrapHandling() { return m_needTrapHandling; }
    106     bool needTrapHandling(Mask mask) { return m_needTrapHandling & mask.bits(); }
    107     void* needTrapHandlingAddress() { return &m_needTrapHandling; }
     191    bool needHandling(BitField mask) const { return m_trapBits.loadRelaxed() & mask; }
     192    void* trapBitsAddress() { return &m_trapBits; }
     193
     194    bool isDeferringTermination() const { return m_deferTerminationCount; }
     195    JS_EXPORT_PRIVATE void deferTermination();
     196    JS_EXPORT_PRIVATE void undoDeferTermination();
    108197
    109198    void notifyGrabAllLocks()
    110199    {
    111         if (needTrapHandling())
     200        if (needHandling(AsyncEvents))
    112201            invalidateCodeBlocksOnStack();
    113202    }
    114203
    115     JS_EXPORT_PRIVATE void fireTrap(EventType);
    116 
    117     void handleTraps(JSGlobalObject*, CallFrame*, VMTraps::Mask);
     204    bool hasTrapBit(Event event, BitField mask)
     205    {
     206        BitField maskedBits = event & mask;
     207        return m_trapBits.loadRelaxed() & maskedBits;
     208    }
     209    void clearTrapBit(Event event) { m_trapBits.exchangeAnd(~event); }
     210    void setTrapBit(Event event)
     211    {
     212        ASSERT((event & ~AllEvents) == 0);
     213        m_trapBits.exchangeOr(event);
     214    }
     215
     216    JS_EXPORT_PRIVATE void fireTrap(Event);
     217    void handleTraps(BitField mask = AsyncEvents);
    118218
    119219    void tryInstallTrapBreakpoints(struct SignalContext&, StackBounds);
     
    122222    VM& vm() const;
    123223
    124     bool hasTrapForEvent(Locker<Lock>&, EventType eventType, Mask mask)
    125     {
    126         ASSERT(eventType < NumberOfEventTypes);
    127         return (m_trapsBitField & mask.bits() & (1 << eventType));
    128     }
    129     void setTrapForEvent(Locker<Lock>&, EventType eventType)
    130     {
    131         ASSERT(eventType < NumberOfEventTypes);
    132         m_trapsBitField |= (1 << eventType);
    133     }
    134     void clearTrapForEvent(Locker<Lock>&, EventType eventType)
    135     {
    136         ASSERT(eventType < NumberOfEventTypes);
    137         m_trapsBitField &= ~(1 << eventType);
    138     }
    139 
    140     EventType takeTopPriorityTrap(Mask);
     224    Event takeTopPriorityTrap(BitField mask);
    141225
    142226#if ENABLE(SIGNAL_BASED_VM_TRAPS)
     
    155239#endif
    156240
     241    static constexpr BitField NeedExceptionHandlingMask = ~(1 << NeedExceptionHandling);
     242
    157243    Box<Lock> m_lock;
    158244    Ref<AutomaticThreadCondition> m_condition;
    159     union {
    160         BitField m_needTrapHandling { 0 };
    161         BitField m_trapsBitField;
    162     };
     245    Atomic<BitField> m_trapBits { 0 };
    163246    bool m_needToInvalidatedCodeBlocks { false };
    164247    bool m_isShuttingDown { false };
     248    bool m_suspendedTerminationException { false };
     249    unsigned m_deferTerminationCount { 0 };
    165250
    166251#if ENABLE(SIGNAL_BASED_VM_TRAPS)
  • trunk/Source/WebCore/ChangeLog

    r275796 r275797  
     12021-04-10  Mark Lam  <mark.lam@apple.com>
     2
     3        Enable VMTraps checks in RETURN_IF_EXCEPTION.
     4        https://bugs.webkit.org/show_bug.cgi?id=224078
     5        rdar://75037057
     6
     7        Reviewed by Keith Miller.
     8
     9        1. Add DeferTermination in WorkerOrWorkletScriptController::initScript().
     10           This allows us to avoid having to make all exception checking in
     11           WorkerOrWorkletScriptController::initScript() very thorough and complete.
     12           Currently, they aren't.
     13
     14        2. Fix WorkerOrWorkletScriptController::evaluate() to handle the TerminationException.
     15
     16        3. Fix JSEventListener::handleEvent() to handle the TerminationException correctly.
     17           Previously, in one case, it was checking scope.exception() for the exception,
     18           but the exception has already been taken out of there.
     19
     20        * bindings/js/JSEventListener.cpp:
     21        (WebCore::JSEventListener::handleEvent):
     22        * workers/WorkerOrWorkletScriptController.cpp:
     23        (WebCore::WorkerOrWorkletScriptController::evaluate):
     24        (WebCore::WorkerOrWorkletScriptController::initScript):
     25
    1262021-04-10  Zalan Bujtas  <zalan@apple.com>
    227
  • trunk/Source/WebCore/bindings/js/JSEventListener.cpp

    r275648 r275797  
    183183
    184184    JSValue thisValue = handleEventFunction == jsFunction ? toJS(lexicalGlobalObject, globalObject, event.currentTarget()) : jsFunction;
    185     NakedPtr<JSC::Exception> exception;
    186     JSValue retval = JSExecState::profiledCall(lexicalGlobalObject, JSC::ProfilingReason::Other, handleEventFunction, callData, thisValue, args, exception);
     185    NakedPtr<JSC::Exception> uncaughtException;
     186    JSValue retval = JSExecState::profiledCall(lexicalGlobalObject, JSC::ProfilingReason::Other, handleEventFunction, callData, thisValue, args, uncaughtException);
    187187
    188188    InspectorInstrumentation::didCallFunction(&scriptExecutionContext);
     
    191191        jsFunctionWindow->setCurrentEvent(savedEvent.get());
    192192
    193     auto handleExceptionIfNeeded = [&] () -> bool {
     193    auto handleExceptionIfNeeded = [&] (JSC::Exception* exception) -> bool {
    194194        if (is<WorkerGlobalScope>(scriptExecutionContext)) {
    195195            auto& scriptController = *downcast<WorkerGlobalScope>(scriptExecutionContext).script();
    196             bool terminatorCausedException = (scope.exception() && vm.isTerminationException(scope.exception()));
     196            bool terminatorCausedException = (exception && vm.isTerminationException(exception));
    197197            if (terminatorCausedException || scriptController.isTerminatingExecution())
    198198                scriptController.forbidExecution();
     
    207207    };
    208208
    209     if (handleExceptionIfNeeded())
     209    if (handleExceptionIfNeeded(uncaughtException))
    210210        return;
    211211
     
    222222            String resultStr = convert<IDLNullable<IDLDOMString>>(*lexicalGlobalObject, retval);
    223223            if (UNLIKELY(scope.exception())) {
    224                 exception = scope.exception();
    225                 if (handleExceptionIfNeeded())
     224                if (handleExceptionIfNeeded(scope.exception()))
    226225                    return;
    227226            }
  • trunk/Source/WebCore/workers/WorkerOrWorkletScriptController.cpp

    r275648 r275797  
    4545#include "WorkerScriptFetcher.h"
    4646#include <JavaScriptCore/Completion.h>
     47#include <JavaScriptCore/DeferTermination.h>
    4748#include <JavaScriptCore/DeferredWorkTimer.h>
    4849#include <JavaScriptCore/Exception.h>
     
    203204        return;
    204205
    205     NakedPtr<JSC::Exception> exception;
    206     evaluate(sourceCode, exception, returnedExceptionMessage);
    207     if (exception) {
    208         JSLockHolder lock(vm());
    209         reportException(m_globalScopeWrapper.get(), exception);
     206    VM& vm = this->vm();
     207    NakedPtr<JSC::Exception> uncaughtException;
     208    evaluate(sourceCode, uncaughtException, returnedExceptionMessage);
     209    if ((uncaughtException && vm.isTerminationException(uncaughtException)) || isTerminatingExecution()) {
     210        forbidExecution();
     211        return;
     212    }
     213    if (uncaughtException) {
     214        JSLockHolder lock(vm);
     215        reportException(m_globalScopeWrapper.get(), uncaughtException);
    210216    }
    211217}
     
    518524void WorkerOrWorkletScriptController::initScript()
    519525{
     526    ASSERT(m_vm.get());
     527    JSC::DeferTermination deferTermination(*m_vm.get());
     528
    520529    if (is<DedicatedWorkerGlobalScope>(m_globalScope)) {
    521530        initScriptWithSubclass<JSDedicatedWorkerGlobalScopePrototype, JSDedicatedWorkerGlobalScope, DedicatedWorkerGlobalScope>();
Note: See TracChangeset for help on using the changeset viewer.