Bug 164441 - TypeProfiler and running GC collection on another thread don't play nicely with each other
Summary: TypeProfiler and running GC collection on another thread don't play nicely wi...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Saam Barati
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2016-11-04 18:07 PDT by Saam Barati
Modified: 2016-11-09 14:05 PST (History)
14 users (show)

See Also:


Attachments
patch (5.99 KB, patch)
2016-11-07 19:23 PST, Saam Barati
ggaren: review+
Details | Formatted Diff | Diff
patch for landing (6.70 KB, patch)
2016-11-09 11:59 PST, Saam Barati
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Saam Barati 2016-11-04 18:07:57 PDT
the type profiler wants to do some JS like work, and call into getOwnPropertySlot, which may allocate stuff, etc.
The GC should communicate to the mutator thread that it should do this stuff before it stops.
Comment 1 Saam Barati 2016-11-06 15:19:47 PST
<rdar://problem/29132174>
Comment 2 Filip Pizlo 2016-11-06 16:49:22 PST
I think that we could make this very clean.  Basically we want to replace all these darn bits with something that allows the collector to supply a lambda of execution to the mutator and then wait for it to do that.

We could do it with these additional fields in Heap:

    Lock m_requestedActivityLock;
    RefPtr<SharedTask<void()>> m_requestedActivity;

We would replace the needFinalizeBit with a requestedActivityBit, and rename finalize() to performRequestedActivity(), which would do:

    clearRequestedActivity(); // CAS loop to clear activity bit and unparkAll().
    {
        LockHolder locker(m_requestedActivityLock);
        if (m_requestedActivity) {
            m_requestedActivity->run();
            m_requestedActivity = nullptr;
        }
    }

In order to request an activity, the collector would do:

    template<typename Func>
    void requestActivity(const Func& func)
    {
        {
            LockHolder locker(m_requestedActivityLock);
            RefPtr<SharedTask<void()>> previousActivity = m_requestedActivity;
            m_requestedActivity = createSharedTask<void()>(
                [=] () {
                    if (previousActivity)
                        previousActivity->run();
                    func();
                });
        }
        setRequestedActivity(); // CAS loop to set activity bit and unparkAll().
    }

I think that this is safe.  There are two atomic steps here on both sides.  In parentheses, I put short names for these atomic steps.

mutator: #1 clear activity bit (M:clear)
mutator: #2 run and clear the activity (M:run)
collector: #1 set the activity (C:setActivity)
collector: #2 set the activity bit (c:setBit)

The two obviously safe executions are:

mutator first: M:clear, M:run, C:setActivity, C:setBit

and

collector first: C:setActivity, C:setBit, M:clear, M:run

We know that each of these steps is atomic and has all of the fences necessary to not be magically reordered.  So, we just need to consider if the following interleavings are valid:

M:clear, C:setActivity, C:setBit, M:run
M:clear, C:setActivity, M:run, C:setBit
C:setActivity, M:clear, M:run, C:setBit
C:setActivity, M:clear, C:setBit, M:run

I don't think there are other interleavings. So let's consider these weird ones! They will be obviously correct if they end up behaving like mutator first or collector first.

M:clear, C:setActivity, C:setBit, M:run: The mutator will run the activities that we requested, but will not clear the bit. That means that the mutator will take a slow path next time it looks at the worldState, and and then it will try to run the bogus activity, but thanks to the null check it will just clear the bit.  So, this behaves just like collector first.

M:clear, C:setActivity, M:run, C:setBit: The mutator will run the activities that we requested, but will not clear the bit. So, this behaves just like collector first.

C:setActivity, M:clear, M:run, C:setBit: The mutator will run the activities that we requested, but will not clear the bit. So, this behaves just like collector first.

C:setActivity, M:clear, C:setBit, M:run: The mutator will run the activities that we requested, but will not clear the bit. So, this behaves just like collector first.

As far as I can tell, there is no way that the collector would request an activity and the mutator would fail to run it prior to regaining heap access. This is true because the only way for the mutator to clear the bit is if the collector runs first without any interleavings. In all other modes, the mutator will leave the bit set. So, the existing logic in stopTheWorldSlow() and friends for dealing with the needFinalizeBit can be reused for this more general requestedActivityBit. We can be sure that so long as the bit is set the mutator will not sleep but will instead try to run the activity. When it does so, it is guaranteed to only clear the bit if it ran the activity without any collector interleaving. If the collector sets an activity before the mutator runs it, the mutator will run it. If the collector sets an activity after the mutator runs the last one, the mutator won't sleep, release heap access, or acquire heap access until it runs that activity.

This would let us combine needFinalizeBit, gcDidJITBit, and whatever new bit you'll need for this.

Then we can convert waitWhileNeedFinalize() into waitForRequestedActivityCompletion(), so that the GC doesn't start the next collection until any requested activities from the last one are done.
Comment 3 Saam Barati 2016-11-07 15:37:11 PST
To solve the issue with the type profiler, I'm going to treat the type profiler log as a root to the GC. I think this issue is orthogonal to Fil's proposal for a more unified  and clean mechanism for asking the mutator to run certain code.
Comment 4 Saam Barati 2016-11-07 19:23:40 PST
Created attachment 294122 [details]
patch
Comment 5 Saam Barati 2016-11-07 19:24:18 PST
Comment on attachment 294122 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=294122&action=review

> JSTests/typeProfiler/type-profiler-gc.js:3
> +// The goal of this test is to just ensure that we don't crash the type profiler.

This test actually doesn't crash on ToT, so I'll need to keep trying to write a test that exercises the fix.
Comment 6 Geoffrey Garen 2016-11-07 20:55:21 PST
Comment on attachment 294122 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=294122&action=review

r=me

> Source/JavaScriptCore/runtime/TypeProfilerLog.cpp:107
> +    LogEntry* entry = m_logStartPtr;
> +    while (entry != m_currentLogEntryPtr) {

Since this loop has a beginning, a condition, and a step, I think it would be clearer as a for loop.
Comment 7 Saam Barati 2016-11-08 11:15:22 PST
Comment on attachment 294122 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=294122&action=review

>> Source/JavaScriptCore/runtime/TypeProfilerLog.cpp:107
>> +    while (entry != m_currentLogEntryPtr) {
> 
> Since this loop has a beginning, a condition, and a step, I think it would be clearer as a for loop.

Sounds good to me. I'll make that change.
Comment 8 Saam Barati 2016-11-09 11:59:42 PST
Created attachment 294261 [details]
patch for landing
Comment 9 WebKit Commit Bot 2016-11-09 14:05:25 PST
Comment on attachment 294261 [details]
patch for landing

Clearing flags on attachment: 294261

Committed r208483: <http://trac.webkit.org/changeset/208483>
Comment 10 WebKit Commit Bot 2016-11-09 14:05:29 PST
All reviewed patches have been landed.  Closing bug.