Bug 194706 - Cache CompactVariableMap::Handle instead of VariableEnvironment for UnlinkedFunctionExecutable
Summary: Cache CompactVariableMap::Handle instead of VariableEnvironment for UnlinkedF...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Tadeu Zagallo
URL:
Keywords: InRadar
Depends on:
Blocks: 194936 194937
  Show dependency treegraph
 
Reported: 2019-02-15 09:07 PST by Tadeu Zagallo
Modified: 2019-02-22 01:09 PST (History)
7 users (show)

See Also:


Attachments
Patch (10.41 KB, patch)
2019-02-15 09:16 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (10.36 KB, patch)
2019-02-19 14:49 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (13.32 KB, patch)
2019-02-20 01:02 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (14.05 KB, patch)
2019-02-20 08:15 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch (14.12 KB, patch)
2019-02-22 00:02 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff
Patch for landing (14.25 KB, patch)
2019-02-22 00:30 PST, Tadeu Zagallo
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Tadeu Zagallo 2019-02-15 09:07:14 PST
Similar to https://bugs.webkit.org/show_bug.cgi?id=194583, but for the bytecode cache.
Comment 1 Tadeu Zagallo 2019-02-15 09:16:57 PST
Created attachment 362118 [details]
Patch
Comment 2 Saam Barati 2019-02-19 09:31:19 PST
Comment on attachment 362118 [details]
Patch

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

I think this is slightly off but I’m not 100% sure. Since Handle is responsible for bumping/decrementing the ref count, I’m not sure we properly model that, since we might decode only N handles, but have the ref count as M. I wonder if it’d make more sense to iteratively build the ref count as we decode handles?

> Source/JavaScriptCore/runtime/CachedTypes.cpp:941
> +        CompactVariableMap* map = new CompactVariableMap;

This looks like a leak to me. How is this supposed to be handled? Maybe this should return Ref, and this should be adoptRef?

> Source/JavaScriptCore/runtime/CachedTypes.cpp:962
> +        handle.m_map = m_map.decode(decoder);

I’m assuming the decoder already hash-conses stuff?

> Source/JavaScriptCore/runtime/CachedTypes.cpp:963
> +        return handle;

It worries me that we somehow don’t end up updating the map’s ref count in place. For example, when we encode a graph of code blocks, there may be Handle’s that live outside that graph. If so, we’re now leaking those VariableEnvironments since we’re encoding a global ref count. I think we should be smart enough to rebuild it local to the map we’re re-creating.
Comment 3 Tadeu Zagallo 2019-02-19 10:19:55 PST
Comment on attachment 362118 [details]
Patch

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

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:941
>> +        CompactVariableMap* map = new CompactVariableMap;
> 
> This looks like a leak to me. How is this supposed to be handled? Maybe this should return Ref, and this should be adoptRef?

This is how CachedPtr is supposed to work right now. CachedRefPtr has a CachedPtr, which it assume starts with a refCount of 1, since we must keep it alive during decoding. Each call to RefPtr decode increases the refCount by 1 and adoptRef's the pointer. When we destroy the Decoder, it derefs all the RefPtrs, to balance the initial refCount.

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:962
>> +        handle.m_map = m_map.decode(decoder);
> 
> I’m assuming the decoder already hash-conses stuff?

Yes, it does.

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:963
>> +        return handle;
> 
> It worries me that we somehow don’t end up updating the map’s ref count in place. For example, when we encode a graph of code blocks, there may be Handle’s that live outside that graph. If so, we’re now leaking those VariableEnvironments since we’re encoding a global ref count. I think we should be smart enough to rebuild it local to the map we’re re-creating.

I'm not sure I follow, but `m_map.decode` will increase the ref count.  We also don't encode the ref count per se, CachedRefPtr is just encoded as a pointer, and its only special behavior is rebuilding the ref count on decode.
Comment 4 Saam Barati 2019-02-19 11:28:46 PST
Comment on attachment 362118 [details]
Patch

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

>>> Source/JavaScriptCore/runtime/CachedTypes.cpp:941
>>> +        CompactVariableMap* map = new CompactVariableMap;
>> 
>> This looks like a leak to me. How is this supposed to be handled? Maybe this should return Ref, and this should be adoptRef?
> 
> This is how CachedPtr is supposed to work right now. CachedRefPtr has a CachedPtr, which it assume starts with a refCount of 1, since we must keep it alive during decoding. Each call to RefPtr decode increases the refCount by 1 and adoptRef's the pointer. When we destroy the Decoder, it derefs all the RefPtrs, to balance the initial refCount.

Got it.

>>> Source/JavaScriptCore/runtime/CachedTypes.cpp:963
>>> +        return handle;
>> 
>> It worries me that we somehow don’t end up updating the map’s ref count in place. For example, when we encode a graph of code blocks, there may be Handle’s that live outside that graph. If so, we’re now leaking those VariableEnvironments since we’re encoding a global ref count. I think we should be smart enough to rebuild it local to the map we’re re-creating.
> 
> I'm not sure I follow, but `m_map.decode` will increase the ref count.  We also don't encode the ref count per se, CachedRefPtr is just encoded as a pointer, and its only special behavior is rebuilding the ref count on decode.

You're misunderstanding my comment here. It's worth just reading the implementation in to see what I'm saying below:
CompactVariableMap::get
and
Handle::~Handle

Basically, the reason we have a HashMap<CompactVariableMapKey, unsigned> instead of HashSet<CompactVariableMapKey> is that we use the value field in the HashMap as a ref count. Each handle is responsible for a +1 ref in that HashMap. When we encode this object, we may only encode a subset of Handles into the map. However, we're also encoding the original ref count of *all* handles. So when we decode the original ref count, we're now in a state where we may leak some CompactVariableEnvironments because our new map has fewer handles than the ref count suggests.
Comment 5 Tadeu Zagallo 2019-02-19 14:49:23 PST
Created attachment 362435 [details]
Patch
Comment 6 Saam Barati 2019-02-19 19:42:19 PST
Comment on attachment 362435 [details]
Patch

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

> Source/JavaScriptCore/runtime/CachedTypes.cpp:901
> +        CompactVariableEnvironment* env = new CompactVariableEnvironment;

I guess I'm still super confused about the lifetime of this object. Who is responsible for deleting this?

- Let's say we are the thing that adds this to the VM's map. The VM's environment map then deletes us when all handles go away? How do we end up being deleted? Basically, I'm worried if CachedPtr has some lifecycle tied to it, do we end up double freeing this object once all handles go away?
- Let's say something identical to us is already in the map, so all handles are actually wrapping the prior thing. Who deletes us?

> Source/JavaScriptCore/runtime/CachedTypes.cpp:922
> +        return decoder.vm().m_compactVariableMap->get(m_environment.decode(decoder), CompactVariableMap::DeleteUnusedEnvironment::No);

see comment above.
Comment 7 Tadeu Zagallo 2019-02-20 00:44:04 PST
Comment on attachment 362435 [details]
Patch

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

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:901
>> +        CompactVariableEnvironment* env = new CompactVariableEnvironment;
> 
> I guess I'm still super confused about the lifetime of this object. Who is responsible for deleting this?
> 
> - Let's say we are the thing that adds this to the VM's map. The VM's environment map then deletes us when all handles go away? How do we end up being deleted? Basically, I'm worried if CachedPtr has some lifecycle tied to it, do we end up double freeing this object once all handles go away?
> - Let's say something identical to us is already in the map, so all handles are actually wrapping the prior thing. Who deletes us?

I think you're right, there's still a bug here.

- I think in this case, it's correct. CachedPtr never frees the pointer, so yes, if we added to the map the pointer will be deleted by ~Handle.
- Here is where I believe we still have the bug. If the "thing identical to us" have been placed in the map by this decoder, then it should be the same pointer, since we hash cons it. The case I did not consider was if the thing identical to us was placed in the map somewhere else.

I think the correct solution is that we need to know if the pointer was newly created or returned from cache and DeleteUnusedEnvironment should reflect it.
Comment 8 Tadeu Zagallo 2019-02-20 01:02:49 PST
Created attachment 362485 [details]
Patch
Comment 9 Saam Barati 2019-02-20 07:39:43 PST
Comment on attachment 362485 [details]
Patch

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

> Source/JavaScriptCore/runtime/CachedTypes.cpp:937
> +        CompactVariableEnvironment* environment = m_environment.decode(decoder, isNewAllocation);

This still doesn’t look right. Suppose we see the environment twice from two handles. Suppose that environment already exists in the VM too. And now we’re decoding the second one and we hash cons to the first one. The first one is now a destructed object.

Maybe what you originally had is closer to correct. I’m just confused whose job it is to free the environment at the end.
Comment 10 Saam Barati 2019-02-20 07:40:48 PST
Comment on attachment 362485 [details]
Patch

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

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:937
>> +        CompactVariableEnvironment* environment = m_environment.decode(decoder, isNewAllocation);
> 
> This still doesn’t look right. Suppose we see the environment twice from two handles. Suppose that environment already exists in the VM too. And now we’re decoding the second one and we hash cons to the first one. The first one is now a destructed object.
> 
> Maybe what you originally had is closer to correct. I’m just confused whose job it is to free the environment at the end.

I meant to write this comment on the LOC below this one
Comment 11 Tadeu Zagallo 2019-02-20 08:15:32 PST
Created attachment 362498 [details]
Patch
Comment 12 Tadeu Zagallo 2019-02-20 08:19:54 PST
Comment on attachment 362485 [details]
Patch

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

>>> Source/JavaScriptCore/runtime/CachedTypes.cpp:937
>>> +        CompactVariableEnvironment* environment = m_environment.decode(decoder, isNewAllocation);
>> 
>> This still doesn’t look right. Suppose we see the environment twice from two handles. Suppose that environment already exists in the VM too. And now we’re decoding the second one and we hash cons to the first one. The first one is now a destructed object.
>> 
>> Maybe what you originally had is closer to correct. I’m just confused whose job it is to free the environment at the end.
> 
> I meant to write this comment on the LOC below this one

That does make sense. It seems that we have to control the lifecycle here, so I added yet another patch that does that. Thanks for all the reviews!
Comment 13 Saam Barati 2019-02-21 17:19:23 PST
Comment on attachment 362498 [details]
Patch

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

> Source/JavaScriptCore/runtime/CachedTypes.cpp:947
> +        return decoder.vm().m_compactVariableMap->get(environment, isNewEntry);
> +        if (isNewAllocation && !isNewEntry) {
> +            decoder.addFinalizer([=] {
> +                delete environment;
> +            });
> +        }

This can't be right ;-)

I vote for adding some tests at this point to ensure you get the behavior you want.
Comment 14 Saam Barati 2019-02-21 17:20:07 PST
Comment on attachment 362498 [details]
Patch

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

> Source/JavaScriptCore/runtime/CachedTypes.cpp:946
> +        if (isNewAllocation && !isNewEntry) {
> +            decoder.addFinalizer([=] {
> +                delete environment;
> +            });

Logically, this looks correct to me. But you should verify with testing once you remove the errant early return.
Comment 15 Tadeu Zagallo 2019-02-21 23:37:18 PST
Comment on attachment 362498 [details]
Patch

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

>> Source/JavaScriptCore/runtime/CachedTypes.cpp:947
>> +        }
> 
> This can't be right ;-)
> 
> I vote for adding some tests at this point to ensure you get the behavior you want.

damn, I can't believe I did this. I don't know why the compiler didn't complain about it either.

I agree that sounds reasonable, it was a lot of iterations. Should I make it an API test?
Comment 16 Saam Barati 2019-02-21 23:53:39 PST
Comment on attachment 362498 [details]
Patch

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

>>> Source/JavaScriptCore/runtime/CachedTypes.cpp:947
>>> +        }
>> 
>> This can't be right ;-)
>> 
>> I vote for adding some tests at this point to ensure you get the behavior you want.
> 
> damn, I can't believe I did this. I don't know why the compiler didn't complain about it either.
> 
> I agree that sounds reasonable, it was a lot of iterations. Should I make it an API test?

Sounds reasonable. Though the more I think about it I’m not sure quite how to test that we free this object...

Also, it might be worth adding a debug assert that says “!isNewEntry if !isNewAllocation”
Comment 17 Tadeu Zagallo 2019-02-21 23:56:41 PST
Comment on attachment 362498 [details]
Patch

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

>>>> Source/JavaScriptCore/runtime/CachedTypes.cpp:947
>>>> +        }
>>> 
>>> This can't be right ;-)
>>> 
>>> I vote for adding some tests at this point to ensure you get the behavior you want.
>> 
>> damn, I can't believe I did this. I don't know why the compiler didn't complain about it either.
>> 
>> I agree that sounds reasonable, it was a lot of iterations. Should I make it an API test?
> 
> Sounds reasonable. Though the more I think about it I’m not sure quite how to test that we free this object...
> 
> Also, it might be worth adding a debug assert that says “!isNewEntry if !isNewAllocation”

Yeah, I'm yet to find a way to test too... I could probably find a way of just encoding/decoding just the handle (without the containing CodeBlock), but I'd have to expose an API for that.

The assertion sounds good, I'll add it.
Comment 18 Tadeu Zagallo 2019-02-22 00:02:40 PST
Created attachment 362700 [details]
Patch
Comment 19 Saam Barati 2019-02-22 00:22:41 PST
Comment on attachment 362700 [details]
Patch

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

r=me

> Source/JavaScriptCore/ChangeLog:12
> +        that we cache the handle instead of the environment.

You should add a sentence why

> Source/JavaScriptCore/runtime/CachedTypes.cpp:380
> +        if (WTF::Optional<ptrdiff_t> offset = encoder.cachedOffsetForPtr(src)) {

Not this patch, but I don’t think the WTF is needed for Optional

> Source/JavaScriptCore/runtime/CachedTypes.cpp:940
> +        CompactVariableEnvironment* environment = m_environment.decode(decoder, isNewAllocation);

This can be a future improvement, but it would be cool if we could skip the hash table lookup for all !isNewAllocation. This could work if we associated a Handle instead of an environment for this pointer. This is sound because a pointer to the same CompactVariableEnvironment* will hash the same. And this is a speed up because hashing CompactVariableEnvironment* isn’t cheap
Comment 20 Tadeu Zagallo 2019-02-22 00:30:57 PST
Created attachment 362702 [details]
Patch for landing
Comment 21 Tadeu Zagallo 2019-02-22 00:33:19 PST
(In reply to Saam Barati from comment #19)
> Comment on attachment 362700 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=362700&action=review
> 
> r=me
> 
> > Source/JavaScriptCore/ChangeLog:12
> > +        that we cache the handle instead of the environment.
> 
> You should add a sentence why
> 
> > Source/JavaScriptCore/runtime/CachedTypes.cpp:380
> > +        if (WTF::Optional<ptrdiff_t> offset = encoder.cachedOffsetForPtr(src)) {
> 
> Not this patch, but I don’t think the WTF is needed for Optional
> 
> > Source/JavaScriptCore/runtime/CachedTypes.cpp:940
> > +        CompactVariableEnvironment* environment = m_environment.decode(decoder, isNewAllocation);
> 
> This can be a future improvement, but it would be cool if we could skip the
> hash table lookup for all !isNewAllocation. This could work if we associated
> a Handle instead of an environment for this pointer. This is sound because a
> pointer to the same CompactVariableEnvironment* will hash the same. And this
> is a speed up because hashing CompactVariableEnvironment* isn’t cheap

I'll do both of these things in follow up patches today. I'll just land this (with the ChangeLog update you requested) since I need these changes to rebase a couple other patches.
Comment 22 WebKit Commit Bot 2019-02-22 01:08:40 PST
Comment on attachment 362702 [details]
Patch for landing

Clearing flags on attachment: 362702

Committed r241938: <https://trac.webkit.org/changeset/241938>
Comment 23 WebKit Commit Bot 2019-02-22 01:08:42 PST
All reviewed patches have been landed.  Closing bug.
Comment 24 Radar WebKit Bug Importer 2019-02-22 01:09:28 PST
<rdar://problem/48305468>