⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 243672 in webkit


Ignore:
Timestamp:
Mar 29, 2019, 6:30:16 PM (7 years ago)
Author:
ysuzuki@apple.com
Message:

[JSC] JSWrapperMap should not use Objective-C Weak map (NSMapTable with NSPointerFunctionsWeakMemory) for m_cachedObjCWrappers
https://bugs.webkit.org/show_bug.cgi?id=196392

Reviewed by Saam Barati.

Weak representation in Objective-C is surprisingly costly in terms of memory. We can see that very easy program shows 10KB memory consumption due to
this weak wrapper map in JavaScriptCore.framework. But we do not need this weak map since Objective-C JSValue has a dealloc. We can unregister itself
from the map when it is deallocated without using Objective-C weak mechanism. And since Objective-C JSValue is tightly coupled to a specific JSContext,
and wrapper map is created per JSContext, JSValue wrapper and actual JavaScriptCore value is one-on-one, and [JSValue dealloc] knows which JSContext's
wrapper map holds itself.

  1. We do not use Objective-C weak mechanism. We use WTF::HashSet instead. When JSValue is allocated, we register it to JSWrapperMap's HashSet. And unregister JSValue from this map when JSValue is deallocated.
  2. We use HashSet<JSValue> (logically) instead of HashMap<JSValueRef, JSValue> to keep JSValueRef and JSValue relationship. We can achieve it because JSValue holds JSValueRef inside it.
  • API/JSContext.mm:

(-[JSContext removeWrapper:]):

  • API/JSContextInternal.h:
  • API/JSValue.mm:

(-[JSValue dealloc]):
(-[JSValue initWithValue:inContext:]):

  • API/JSWrapperMap.h:
  • API/JSWrapperMap.mm:

(WrapperKey::hashTableDeletedValue):
(WrapperKey::WrapperKey):
(WrapperKey::isHashTableDeletedValue const):
(WrapperKey::Hash::hash):
(WrapperKey::Hash::equal):
(WrapperKey::Traits::isEmptyValue):
(WrapperKey::Translator::hash):
(WrapperKey::Translator::equal):
(WrapperKey::Translator::translate):
(-[JSWrapperMap initWithGlobalContextRef:]):
(-[JSWrapperMap dealloc]):
(-[JSWrapperMap objcWrapperForJSValueRef:inContext:]):
(-[JSWrapperMap removeWrapper:]):

  • API/tests/testapi.mm:

(testObjectiveCAPIMain):

Location:
trunk/Source/JavaScriptCore
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/API/JSContext.mm

    r243617 r243672  
    370370}
    371371
     372- (void)removeWrapper:(JSValue *)value
     373{
     374    return [[self wrapperMap] removeWrapper:value];
     375}
     376
    372377+ (JSContext *)contextWithJSGlobalContextRef:(JSGlobalContextRef)globalContext
    373378{
  • trunk/Source/JavaScriptCore/API/JSContextInternal.h

    r240511 r243672  
    5555- (JSValue *)wrapperForObjCObject:(id)object;
    5656- (JSValue *)wrapperForJSObject:(JSValueRef)value;
     57- (void)removeWrapper:(JSValue *)value;
    5758
    5859@end
  • trunk/Source/JavaScriptCore/API/JSValue.mm

    r238074 r243672  
    7272- (void)dealloc
    7373{
     74    [_context removeWrapper:self];
    7475    JSValueUnprotect([_context JSGlobalContextRef], m_value);
    7576    [_context release];
     
    10761077        return nil;
    10771078
     1079    ASSERT(context);
    10781080    _context = [context retain];
    10791081    m_value = value;
  • trunk/Source/JavaScriptCore/API/JSWrapperMap.h

    r218379 r243672  
    3838- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value inContext:(JSContext *)context;
    3939
     40- (void)removeWrapper:(JSValue *)wrapper;
     41
    4042@end
    4143
  • trunk/Source/JavaScriptCore/API/JSWrapperMap.mm

    r241956 r243672  
    582582@end
    583583
     584struct WrapperKey {
     585    static constexpr uintptr_t hashTableDeletedValue() { return 1; }
     586
     587    WrapperKey() = default;
     588
     589    explicit WrapperKey(WTF::HashTableDeletedValueType)
     590        : m_wrapper(reinterpret_cast<JSValue *>(hashTableDeletedValue()))
     591    {
     592    }
     593
     594    explicit WrapperKey(JSValue *wrapper)
     595        : m_wrapper(wrapper)
     596    {
     597    }
     598
     599    bool isHashTableDeletedValue() const
     600    {
     601        return reinterpret_cast<uintptr_t>(m_wrapper) == hashTableDeletedValue();
     602    }
     603
     604    __unsafe_unretained JSValue *m_wrapper { nil };
     605
     606    struct Hash {
     607        static unsigned hash(const WrapperKey& key)
     608        {
     609            return DefaultHash<JSValueRef>::Hash::hash([key.m_wrapper JSValueRef]);
     610        }
     611
     612        static bool equal(const WrapperKey& lhs, const WrapperKey& rhs)
     613        {
     614            return lhs.m_wrapper == rhs.m_wrapper;
     615        }
     616
     617        static const bool safeToCompareToEmptyOrDeleted = false;
     618    };
     619
     620    struct Traits : public SimpleClassHashTraits<WrapperKey> {
     621        static const bool hasIsEmptyValueFunction = true;
     622        static bool isEmptyValue(const WrapperKey& key)
     623        {
     624            return key.m_wrapper == nullptr;
     625        }
     626    };
     627
     628    struct Translator {
     629        struct ValueAndContext {
     630            __unsafe_unretained JSContext *m_context;
     631            JSValueRef m_value;
     632        };
     633
     634        static unsigned hash(const ValueAndContext& value)
     635        {
     636            return DefaultHash<JSValueRef>::Hash::hash(value.m_value);
     637        }
     638
     639        static bool equal(const WrapperKey& lhs, const ValueAndContext& value)
     640        {
     641            return [lhs.m_wrapper JSValueRef] == value.m_value;
     642        }
     643
     644        static void translate(WrapperKey& result, const ValueAndContext& value, unsigned)
     645        {
     646            result = WrapperKey([[[JSValue alloc] initWithValue:value.m_value inContext:value.m_context] autorelease]);
     647        }
     648    };
     649};
     650
    584651@implementation JSWrapperMap {
    585652    NSMutableDictionary *m_classMap;
    586653    std::unique_ptr<JSC::WeakGCMap<__unsafe_unretained id, JSC::JSObject>> m_cachedJSWrappers;
    587     NSMapTable *m_cachedObjCWrappers;
     654    HashSet<WrapperKey, WrapperKey::Hash, WrapperKey::Traits> m_cachedObjCWrappers;
    588655}
    589656
     
    593660    if (!self)
    594661        return nil;
    595 
    596     NSPointerFunctionsOptions keyOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality;
    597     NSPointerFunctionsOptions valueOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
    598     m_cachedObjCWrappers = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];
    599662
    600663    m_cachedJSWrappers = std::make_unique<JSC::WeakGCMap<__unsafe_unretained id, JSC::JSObject>>(toJS(context)->vm());
     
    608671- (void)dealloc
    609672{
    610     [m_cachedObjCWrappers release];
    611673    [m_classMap release];
    612674    [super dealloc];
     
    663725{
    664726    ASSERT(toJSGlobalObject([context JSGlobalContextRef])->wrapperMap() == self);
    665     JSValue *wrapper = (__bridge JSValue *)NSMapGet(m_cachedObjCWrappers, value);
    666     if (!wrapper) {
    667         wrapper = [[[JSValue alloc] initWithValue:value inContext:context] autorelease];
    668         NSMapInsert(m_cachedObjCWrappers, value, (__bridge void*)wrapper);
    669     }
    670     return wrapper;
     727    WrapperKey::Translator::ValueAndContext valueAndContext { context, value };
     728    auto addResult = m_cachedObjCWrappers.add<WrapperKey::Translator>(valueAndContext);
     729    return addResult.iterator->m_wrapper;
     730}
     731
     732- (void)removeWrapper:(JSValue *)wrapper
     733{
     734    m_cachedObjCWrappers.remove(WrapperKey(wrapper));
    671735}
    672736
  • trunk/Source/JavaScriptCore/API/tests/testapi.mm

    r242982 r243672  
    566566{
    567567    @autoreleasepool {
    568         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
    569         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
     568        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     569        JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
    570570        [context evaluateScript:@"bad"];
     571    }
     572
     573    @autoreleasepool {
     574        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     575        JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
     576        JSValue *number1 = [context evaluateScript:@"42092389"];
     577        JSValue *number2 = [context evaluateScript:@"42092389"];
     578        checkResult(@"wrapper cache for numbers", number1 == number2 && number1.isNumber && [number1 toInt32] == 42092389);
     579    }
     580
     581    @autoreleasepool {
     582        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
     583        JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
     584        JSValue *object1 = [context evaluateScript:@"({})"];
     585        JSValue *object2 = [context evaluateScript:@"({})"];
     586        checkResult(@"wrapper cache for objects", object1 != object2);
    571587    }
    572588
  • trunk/Source/JavaScriptCore/ChangeLog

    r243670 r243672  
     12019-03-29  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] JSWrapperMap should not use Objective-C Weak map (NSMapTable with NSPointerFunctionsWeakMemory) for m_cachedObjCWrappers
     4        https://bugs.webkit.org/show_bug.cgi?id=196392
     5
     6        Reviewed by Saam Barati.
     7
     8        Weak representation in Objective-C is surprisingly costly in terms of memory. We can see that very easy program shows 10KB memory consumption due to
     9        this weak wrapper map in JavaScriptCore.framework. But we do not need this weak map since Objective-C JSValue has a dealloc. We can unregister itself
     10        from the map when it is deallocated without using Objective-C weak mechanism. And since Objective-C JSValue is tightly coupled to a specific JSContext,
     11        and wrapper map is created per JSContext, JSValue wrapper and actual JavaScriptCore value is one-on-one, and [JSValue dealloc] knows which JSContext's
     12        wrapper map holds itself.
     13
     14        1. We do not use Objective-C weak mechanism. We use WTF::HashSet instead. When JSValue is allocated, we register it to JSWrapperMap's HashSet. And unregister
     15           JSValue from this map when JSValue is deallocated.
     16        2. We use HashSet<JSValue> (logically) instead of HashMap<JSValueRef, JSValue> to keep JSValueRef and JSValue relationship. We can achieve it because JSValue
     17           holds JSValueRef inside it.
     18
     19        * API/JSContext.mm:
     20        (-[JSContext removeWrapper:]):
     21        * API/JSContextInternal.h:
     22        * API/JSValue.mm:
     23        (-[JSValue dealloc]):
     24        (-[JSValue initWithValue:inContext:]):
     25        * API/JSWrapperMap.h:
     26        * API/JSWrapperMap.mm:
     27        (WrapperKey::hashTableDeletedValue):
     28        (WrapperKey::WrapperKey):
     29        (WrapperKey::isHashTableDeletedValue const):
     30        (WrapperKey::Hash::hash):
     31        (WrapperKey::Hash::equal):
     32        (WrapperKey::Traits::isEmptyValue):
     33        (WrapperKey::Translator::hash):
     34        (WrapperKey::Translator::equal):
     35        (WrapperKey::Translator::translate):
     36        (-[JSWrapperMap initWithGlobalContextRef:]):
     37        (-[JSWrapperMap dealloc]):
     38        (-[JSWrapperMap objcWrapperForJSValueRef:inContext:]):
     39        (-[JSWrapperMap removeWrapper:]):
     40        * API/tests/testapi.mm:
     41        (testObjectiveCAPIMain):
     42
    1432019-03-29  Robin Morisset  <rmorisset@apple.com>
    244
Note: See TracChangeset for help on using the changeset viewer.