Bug 141106 - Implement ES6 Object.getOwnPropertySymbols
Summary: Implement ES6 Object.getOwnPropertySymbols
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: New Bugs (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Yusuke Suzuki
URL:
Keywords:
Depends on: 143146 143276
Blocks: 141279 143375
  Show dependency treegraph
 
Reported: 2015-01-30 18:12 PST by Yusuke Suzuki
Modified: 2015-04-04 07:33 PDT (History)
9 users (show)

See Also:


Attachments
Patch (153.32 KB, patch)
2015-02-06 10:02 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (154.42 KB, patch)
2015-02-06 22:18 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (155.89 KB, patch)
2015-02-07 12:56 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (159.02 KB, patch)
2015-02-07 13:56 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (145.97 KB, patch)
2015-02-11 04:27 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (147.77 KB, patch)
2015-02-16 20:07 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (144.23 KB, patch)
2015-02-25 10:23 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (253.65 KB, patch)
2015-02-28 17:58 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (253.86 KB, patch)
2015-02-28 18:07 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (253.42 KB, patch)
2015-03-03 21:19 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (248.43 KB, patch)
2015-03-04 23:05 PST, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (259.88 KB, patch)
2015-03-09 12:07 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (271.90 KB, patch)
2015-03-14 00:27 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (263.80 KB, patch)
2015-03-14 06:51 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (266.36 KB, patch)
2015-03-22 13:36 PDT, Yusuke Suzuki
no flags Details | Formatted Diff | Diff
Patch (35.92 KB, patch)
2015-04-02 19:59 PDT, Yusuke Suzuki
ggaren: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Yusuke Suzuki 2015-01-30 18:12:59 PST
Implement ES6 Object.getOwnPropertySymbols
Comment 1 Yusuke Suzuki 2015-01-30 18:24:41 PST
One issue is that, it can expose property symbols of the target object.
Currently, JSC priviledge code uses *private* symbols. For example, in builtin/, @Object is used to extract builtin Object. Object.getOwnPropertySymbols(window) should not expose these private Symbols.

So, here's a roadmap.

1. Introduce a new flag to StringImpl*, which represents the target Symbol is truly private Symbol. (Maybe, isPrivate).
2. Rename JSObject::getOwnPropertyNames to JSObject::getOwnPropertyKeys to align to the ES6 spec, and accept EnumerationType parameter (String or Symbol).
3. May rename getPropertyNames to getPropertyKeys, Name is now used for representing String property in the ES6 spec.
4. Implement Object.getOwnPropertySymbols by leveraging this JSObject::getOwnPropertySymbols.
Comment 2 Yusuke Suzuki 2015-02-06 10:02:25 PST
Created attachment 246164 [details]
Patch
Comment 3 Yusuke Suzuki 2015-02-06 10:03:47 PST
Comment on attachment 246164 [details]
Patch

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

Added comments.

> Source/JavaScriptCore/ChangeLog:18
> +        This conservative assumption doesn't break the callers.

Currently we doesn't rename `getOwnPropertyNames` to `getOwnPropertyKey` which is used in the ES6 draft spec.
If it's preferrable, I'll rename it.
Comment 4 Yusuke Suzuki 2015-02-06 22:18:34 PST
Created attachment 246199 [details]
Patch
Comment 5 Yusuke Suzuki 2015-02-07 12:56:49 PST
Created attachment 246213 [details]
Patch
Comment 6 Yusuke Suzuki 2015-02-07 13:56:57 PST
Created attachment 246215 [details]
Patch
Comment 7 Darin Adler 2015-02-07 15:44:42 PST
Gavin might be a good reviewer for this.
Comment 8 Darin Adler 2015-02-07 15:44:56 PST
Comment on attachment 246215 [details]
Patch

Is adding a bit to StringImpl the best way to distinguish these? It seems a shame to have to build a feature into WTF just for this. It’s OK if there’s no other efficient way.
Comment 9 Yusuke Suzuki 2015-02-08 03:04:42 PST
(In reply to comment #8)
> Comment on attachment 246215 [details]
> Patch
> 
> Is adding a bit to StringImpl the best way to distinguish these? It seems a
> shame to have to build a feature into WTF just for this. It’s OK if there’s
> no other efficient way.

Thank you for adding reviewers and commenting on the patch.

StringImpl pointer is now used in PropertyTable directly.
So adding a status to StringImpl is the most efficient way. And if we add the flag to PropertyTable Key, the memory usage of PropertyTable is doubled. Or if we create some structure holding StringImpl* and status and strore it to PropertyTable, we need to refer StringImpl indirectly and additional heap allocation is needed.

One possible solution is storing a flag to StringImpl*'s lower bit in PropertyTable (due to alignment, some space is reserved), but it seems that it's a little bit hacky.
Comment 10 Darin Adler 2015-02-08 09:35:18 PST
Comment on attachment 246215 [details]
Patch

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

Still not comfortable reviewing the whole thing, but I have a few more small comments.

> Source/JavaScriptCore/runtime/EnumerationMode.h:38
> +enum class EnumerationType {

I think we should combine both EnumerationMode and EnumerationType into a single integer with 3 orthogonal bits.

> Source/JavaScriptCore/runtime/EnumerationMode.h:40
> +    IncludeSymbol,
> +    ExcludeSymbol

Should be Include/ExcludeSymbols, plural.

> Source/WTF/wtf/text/StringImpl.h:386
> +    ALWAYS_INLINE static Ref<StringImpl> createUniquePrivate()

I think this still needs the name empty in it, to specify that the unique private string will have a value of “empty string”.

> Source/WTF/wtf/text/StringImpl.h:403
>      static unsigned flagIsAtomic() { return s_hashFlagIsAtomic; }
>      static unsigned flagIsUnique() { return s_hashFlagIsUnique; }
> +    static unsigned flagIsPrivate() { return s_hashFlagIsPrivate; }

It’s worth noting that these three flags are not orthogonal. Atomic and Unique are mutually exclusive. Private can only be set if Unique is also set. So I think there are only 4 states here even though we are using 3 bits: 1) non-atomic, non-unique, non-private 2) atomic, 3) unique, 4) unique private.
Comment 11 Yusuke Suzuki 2015-02-09 10:21:12 PST
Comment on attachment 246215 [details]
Patch

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

Thank you for your comment!

>> Source/JavaScriptCore/runtime/EnumerationMode.h:38
>> +enum class EnumerationType {
> 
> I think we should combine both EnumerationMode and EnumerationType into a single integer with 3 orthogonal bits.

Ah, sounds nice.
What do you think introducing `class EnumerationMode` that wraps `unsigned flags` and provides `shouldSkipJSObject()`, `shouldIncludeSymbols()` and `shouldIncludeDontEnumProperties()` member functions?
I think it leverages type checks and cleans up predicate functions.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:40
>> +    ExcludeSymbol
> 
> Should be Include/ExcludeSymbols, plural.

Thanks! I'll fix it.

>> Source/WTF/wtf/text/StringImpl.h:386
>> +    ALWAYS_INLINE static Ref<StringImpl> createUniquePrivate()
> 
> I think this still needs the name empty in it, to specify that the unique private string will have a value of “empty string”.

I'll fix it.

>> Source/WTF/wtf/text/StringImpl.h:403
>> +    static unsigned flagIsPrivate() { return s_hashFlagIsPrivate; }
> 
> It’s worth noting that these three flags are not orthogonal. Atomic and Unique are mutually exclusive. Private can only be set if Unique is also set. So I think there are only 4 states here even though we are using 3 bits: 1) non-atomic, non-unique, non-private 2) atomic, 3) unique, 4) unique private.

Sounds nice.
So I'll introduce 2-bit space to flags instead of 3-bit. And folding above 4 types here.
Comment 12 Yusuke Suzuki 2015-02-09 22:26:33 PST
Comment on attachment 246215 [details]
Patch

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

> Source/WTF/wtf/text/StringImpl.h:797
>      static const unsigned s_hashFlagIsAtomic = 1u << 4;

And seeing the current implementation, `1u << 2` case is missing. So we can drop this.
Comment 13 Yusuke Suzuki 2015-02-11 04:27:55 PST
Created attachment 246384 [details]
Patch
Comment 14 Yusuke Suzuki 2015-02-16 20:07:32 PST
Created attachment 246719 [details]
Patch
Comment 15 Yusuke Suzuki 2015-02-16 20:08:08 PST
Comment on attachment 246719 [details]
Patch

Updated the patch, ready for review.
Comment 16 Yusuke Suzuki 2015-02-23 21:14:37 PST
Comment on attachment 246719 [details]
Patch

This should be behind the runtime flag, SymbolEnabled.
I'll update the revised patch.
Comment 17 Yusuke Suzuki 2015-02-25 10:23:27 PST
Created attachment 247333 [details]
Patch
Comment 18 Darin Adler 2015-02-28 14:07:03 PST
Comment on attachment 247333 [details]
Patch

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

I think we do want this feature, and this patch seems basically right to me.

I worry a little about how wordy the EnumerationMode::IncludeDontEnumPropertiesAndExcludeSymbols becomes with this change. And I also worry a little about the clarity about when we need to use Identifier::from vs. the Identifier constructor directly. With two functions that can take arguments of the same types but do different jobs, I think we need clearer naming than just “constructor” and “from”.

> Source/JavaScriptCore/ChangeLog:104
> +        In the a lot of place, `Identifier(VM*/ExecState*, StringImpl*)` constructor is used.
> +        However, it's ambiguous because `StringImpl*` has 2 different meanings.
> +        1) normal string, it is replacable with `WTFString` and 2) `uid`, which holds `isUnique` information to represent Symbols.
> +        So we dropped `Identifier(VM*/ExecState*, StringImpl*)` and instead, introduced `Identifier::from(VM*/ExecState*, StringImpl*)`.
> +        This function is used for 2) `uid`. So uniqueness of `StringImpl*` is saved.
> +        To just construct `Identifier` from string type, we can use `Identifier(VM*/ExecState*, const WTFString&)` instead.

Having two separate functions seems fine. But having one be the constructor and the other be named “from” does not make the difference between these functions clear at all. We need to name the functions to explain the difference in their behavior.

> Source/JavaScriptCore/JavaScriptCore.order:-940
> -__ZN3JSC9Structure29getPropertyNamesFromStructureERNS_2VMERNS_17PropertyNameArrayENS_15EnumerationModeE

No need to edit order files. These are generated files that need not be updated by hand. If you rebase this patch, feel free to omit the changes to this file.

> Source/JavaScriptCore/runtime/EnumerationMode.h:44
> +    inline EnumerationMode(EnumerationModeRepresentationTag tag)

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/EnumerationMode.h:49
> +    inline bool shouldIncludeDontEnumProperties() const

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/EnumerationMode.h:54
> +    inline bool shouldIncludeJSObjectPropertyNames() const

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/EnumerationMode.h:59
> +    inline bool shouldIncludeSymbols() const

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/EnumerationMode.h:64
> +    static inline EnumerationMode createThatSkipsJSObject(EnumerationMode mode)

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/EnumerationMode.h:70
> +    inline explicit EnumerationMode(unsigned flags)

The inline keyword here is not needed and should be omitted.

> Source/JavaScriptCore/runtime/Identifier.h:52
> +    Identifier(ExecState* exec, const String& s)
> +        : Identifier(exec, s.impl())
> +    {
> +    }

Once a function becomes multi-line like this, we should move the implementation out of the class definition and just mark it inline explicitly instead. It should go into the IdentifierInlines.h file. The reason is that we want the class definition to be readable and give an overview of the class's behavior, and that is no longer true if we have lots of function bodies. This class already has a bit of a problem with that, but adding more function bodies will make it even worse.

> Source/JavaScriptCore/runtime/Identifier.h:63
> +    Identifier(VM* vm, const String& s)
> +        : Identifier(vm, s.impl())
> +    {
> +    }

Shouldn’t this have an assertion: !s.isUnique()?

> Source/JavaScriptCore/runtime/Identifier.h:73
> +    static Identifier from(VM* vm, StringImpl* uid)

I don’t think that “from” is a suitably clear function name to distinguish the version that works with unique strings from the version that prohibits unique strings.
Comment 19 Yusuke Suzuki 2015-02-28 14:25:03 PST
Comment on attachment 247333 [details]
Patch

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

Thank you for your review!
For IncludeDontEnumPropertiesAndExcludeSymbols, what do you think of making ExcludeSymbols as default and drop its name from Enum. It makes IncludeDontEnumPropertiesAndExcludeSymbols to IncludeDontEnumProperties. And when including Symbols, IncludeDontEnumPropertiesAndIncludeSymbols is used.

>> Source/JavaScriptCore/ChangeLog:104
>> +        To just construct `Identifier` from string type, we can use `Identifier(VM*/ExecState*, const WTFString&)` instead.
> 
> Having two separate functions seems fine. But having one be the constructor and the other be named “from” does not make the difference between these functions clear at all. We need to name the functions to explain the difference in their behavior.

That's reasonable.
So I suggest that, dropping public identifier constructor for String and expose 2 functions,
* Identifier::fromUid
* Identifier::fromString

What do you think of?

>> Source/JavaScriptCore/JavaScriptCore.order:-940
>> -__ZN3JSC9Structure29getPropertyNamesFromStructureERNS_2VMERNS_17PropertyNameArrayENS_15EnumerationModeE
> 
> No need to edit order files. These are generated files that need not be updated by hand. If you rebase this patch, feel free to omit the changes to this file.

Thanks! I'll rebase it without order file change.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:44
>> +    inline EnumerationMode(EnumerationModeRepresentationTag tag)
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:49
>> +    inline bool shouldIncludeDontEnumProperties() const
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:54
>> +    inline bool shouldIncludeJSObjectPropertyNames() const
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:59
>> +    inline bool shouldIncludeSymbols() const
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:64
>> +    static inline EnumerationMode createThatSkipsJSObject(EnumerationMode mode)
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/EnumerationMode.h:70
>> +    inline explicit EnumerationMode(unsigned flags)
> 
> The inline keyword here is not needed and should be omitted.

Fixed.

>> Source/JavaScriptCore/runtime/Identifier.h:52
>> +    }
> 
> Once a function becomes multi-line like this, we should move the implementation out of the class definition and just mark it inline explicitly instead. It should go into the IdentifierInlines.h file. The reason is that we want the class definition to be readable and give an overview of the class's behavior, and that is no longer true if we have lots of function bodies. This class already has a bit of a problem with that, but adding more function bodies will make it even worse.

Make sense. Moving the definition to IdentifierInline.h and marking it with inline keeps readability.
I'll fix it.

>> Source/JavaScriptCore/runtime/Identifier.h:63
>> +    }
> 
> Shouldn’t this have an assertion: !s.isUnique()?

Should insert it! Thx.

>> Source/JavaScriptCore/runtime/Identifier.h:73
>> +    static Identifier from(VM* vm, StringImpl* uid)
> 
> I don’t think that “from” is a suitably clear function name to distinguish the version that works with unique strings from the version that prohibits unique strings.

So I suggest the 2 functions.
* fromUid(VM* vm, StringImpl* uid)
It accepts unique id and non-unique id StringImpl*, but it keeps uniquness of StringImpl* held by Identifier.

* fromString(VM* vm, const String&)
It just generates Identifier from String. It guaranteed that StringImpl* held by generated Identifier is not unique.
Comment 20 Yusuke Suzuki 2015-02-28 17:58:26 PST
Created attachment 247619 [details]
Patch
Comment 21 WebKit Commit Bot 2015-02-28 18:01:19 PST
Attachment 247619 [details] did not pass style-queue:


ERROR: Source/JavaScriptCore/ChangeLog:29:  Line contains tab character.  [whitespace/tab] [5]
ERROR: Source/JavaScriptCore/ChangeLog:30:  Line contains tab character.  [whitespace/tab] [5]
ERROR: Source/JavaScriptCore/ChangeLog:31:  Line contains tab character.  [whitespace/tab] [5]
ERROR: Source/JavaScriptCore/ChangeLog:32:  Line contains tab character.  [whitespace/tab] [5]
Total errors found: 4 in 139 files


If any of these errors are false positives, please file a bug against check-webkit-style.
Comment 22 Yusuke Suzuki 2015-02-28 18:07:38 PST
Created attachment 247620 [details]
Patch
Comment 23 Yusuke Suzuki 2015-03-03 18:51:03 PST
Could any JavaScriptCore people take a look at this patch?
Comment 24 Filip Pizlo 2015-03-03 20:06:53 PST
Comment on attachment 247620 [details]
Patch

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

> Source/JavaScriptCore/runtime/Identifier.h:64
> +    template<unsigned charactersCount>
> +    static Identifier fromString(VM*, const char (&characters)[charactersCount]);
> +    template<unsigned charactersCount>
> +    static Identifier fromString(ExecState*, const char (&characters)[charactersCount]);
> +    static Identifier fromString(VM*, const LChar*, int length);
> +    static Identifier fromString(VM*, const UChar*, int length);
> +    static Identifier fromString(VM*, const String&);
> +    static Identifier fromString(ExecState*, AtomicStringImpl*);
> +    static Identifier fromString(ExecState*, const AtomicString&);
> +    static Identifier fromString(ExecState*, const String&);
> +    static Identifier fromString(ExecState*, const char*);
> +
> +    static Identifier fromUid(VM*, StringImpl* uid);
> +    static Identifier fromUid(ExecState*, StringImpl* uid);
> +    static Identifier fromUid(const PrivateName&);

You should have a beefy comment here to describe the difference, and how that difference is used.
Comment 25 Yusuke Suzuki 2015-03-03 21:19:15 PST
Created attachment 247836 [details]
Patch
Comment 26 Yusuke Suzuki 2015-03-03 21:20:57 PST
Comment on attachment 247620 [details]
Patch

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

>> Source/JavaScriptCore/runtime/Identifier.h:64
>> +    static Identifier fromUid(const PrivateName&);
> 
> You should have a beefy comment here to describe the difference, and how that difference is used.

Thank you! I've added comments here and updated the revised patch :D
Comment 27 Filip Pizlo 2015-03-04 20:06:48 PST
Comment on attachment 247836 [details]
Patch

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

> Source/JavaScriptCore/ChangeLog:11
> +        This patch implements `Object.getOwnPropertySymbols`.
> +        One technical issue is that, since we use private symbols (such as `@Object`) in the
> +        privileged JS code in `builtin/`, they should not be exposed.
> +        To distinguish them from the usual symbols, `StringImpl*` represents whether it's private or not.

Is this really the only way?  If it's just builtin symbols, then you could imagine maintaining a set of them.  Like, literally HashSet<StringImpl*> JSGlobalObject::m_privates.

Did you consider that?
Comment 28 Yusuke Suzuki 2015-03-04 20:28:04 PST
Comment on attachment 247836 [details]
Patch

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

Thank you for your review!

>> Source/JavaScriptCore/ChangeLog:11
>> +        To distinguish them from the usual symbols, `StringImpl*` represents whether it's private or not.
> 
> Is this really the only way?  If it's just builtin symbols, then you could imagine maintaining a set of them.  Like, literally HashSet<StringImpl*> JSGlobalObject::m_privates.
> 
> Did you consider that?

Great pointing! Your suggestion looks very nice.
Marking private symbols with non-intrusive way has Pros/Cons.

Pros:
+ Dropping private information from StringImpl*, this is not essential information belongging to StringImpl* (btw, it doesn't reduce bit flag space since atomic & unique flags still occupies 2bits)

Cons:
+ We need to filter private symbols with hash set lookups in the phase of adding property names to PropertyNamesArray. It looks costly.

After considering about these pros/cons, I think your suggestion looks very clean way.
And performance cost would be limited because we need to filter private symbols only when the target StringImpl* is unique and we need to IncludeSymbols. So this performance impact only affects on Object.getOwnPropertySymbols case.
What do you think of?
Comment 29 Yusuke Suzuki 2015-03-04 23:05:08 PST
Created attachment 247933 [details]
Patch
Comment 30 Filip Pizlo 2015-03-04 23:09:47 PST
(In reply to comment #28)
> Comment on attachment 247836 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=247836&action=review
> 
> Thank you for your review!
> 
> >> Source/JavaScriptCore/ChangeLog:11
> >> +        To distinguish them from the usual symbols, `StringImpl*` represents whether it's private or not.
> > 
> > Is this really the only way?  If it's just builtin symbols, then you could imagine maintaining a set of them.  Like, literally HashSet<StringImpl*> JSGlobalObject::m_privates.
> > 
> > Did you consider that?
> 
> Great pointing! Your suggestion looks very nice.
> Marking private symbols with non-intrusive way has Pros/Cons.
> 
> Pros:
> + Dropping private information from StringImpl*, this is not essential
> information belongging to StringImpl* (btw, it doesn't reduce bit flag space
> since atomic & unique flags still occupies 2bits)
> 
> Cons:
> + We need to filter private symbols with hash set lookups in the phase of
> adding property names to PropertyNamesArray. It looks costly.
> 
> After considering about these pros/cons, I think your suggestion looks very
> clean way.
> And performance cost would be limited because we need to filter private
> symbols only when the target StringImpl* is unique and we need to
> IncludeSymbols. So this performance impact only affects on
> Object.getOwnPropertySymbols case.
> What do you think of?

It's an interesting question.  I'd like to hear ggaren's thoughts on this.  I'm on the fence.  I like that your approach makes Object.getOwnPropertySymbols fast.  On the other hand, hash lookups can be made fast.  It's a tough call, so I'd like to see what others think.
Comment 31 Yusuke Suzuki 2015-03-04 23:42:21 PST
Comment on attachment 247836 [details]
Patch

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

>>>> Source/JavaScriptCore/ChangeLog:11
>>>> +        To distinguish them from the usual symbols, `StringImpl*` represents whether it's private or not.
>>> 
>>> Is this really the only way?  If it's just builtin symbols, then you could imagine maintaining a set of them.  Like, literally HashSet<StringImpl*> JSGlobalObject::m_privates.
>>> 
>>> Did you consider that?
>> 
>> Great pointing! Your suggestion looks very nice.
>> Marking private symbols with non-intrusive way has Pros/Cons.
>> 
>> Pros:
>> + Dropping private information from StringImpl*, this is not essential information belongging to StringImpl* (btw, it doesn't reduce bit flag space since atomic & unique flags still occupies 2bits)
>> 
>> Cons:
>> + We need to filter private symbols with hash set lookups in the phase of adding property names to PropertyNamesArray. It looks costly.
>> 
>> After considering about these pros/cons, I think your suggestion looks very clean way.
>> And performance cost would be limited because we need to filter private symbols only when the target StringImpl* is unique and we need to IncludeSymbols. So this performance impact only affects on Object.getOwnPropertySymbols case.
>> What do you think of?
> 
> It's an interesting question.  I'd like to hear ggaren's thoughts on this.  I'm on the fence.  I like that your approach makes Object.getOwnPropertySymbols fast.  On the other hand, hash lookups can be made fast.  It's a tough call, so I'd like to see what others think.

And one more possible solution is, folding private bit into StringImpl* itself.
Like following code,

class PropertyName {
public:
    PropertyName = delete;

    static PropertyName* createPublicName(StringImpl* impl)
    {
        return reinterpret_cast<PropertyName*>(impl);
    }

    static PropertyName* createPrivateName(StringImpl* impl)
    {
        uintptr_t pointer = reinterpret_cast<uintptr_t>(impl);
        return reinterpret_cast<PropertyName*>(pointer | 0x1);
    }

    bool isPrivateName() const
    {
        return reinterpret_cast<uintptr_t>(this) & 0x1;
    }

    void ref()
    {
        extractStringImpl()->ref();
    }

    bool hasOneRef() const
    {
        return extractStringImpl()->hasOneRef();
    }

    unsigned refCount() const
    {
        return extractStringImpl()->refCount();
    }

    void deref()
    {
        extractStringImpl()->deref();
    }

private:
    StringImpl* extractStringImpl() const
    {
        return reinterpret_cast<StringImpl*>(reinterpret_cast<uintptr_t>(this) & ~(0x1ULL));
    }
};

And storing this PropertyName* into PropertyTable.

Pros:
1. Very efficient
2. Private bit is separated from StringImpl*

Cons:
1. A bit complicated implementation
2. Possibly large changes are required in JSC
Comment 32 Geoffrey Garen 2015-03-09 08:20:09 PDT
> > After considering about these pros/cons, I think your suggestion looks very
> > clean way.
> > And performance cost would be limited because we need to filter private
> > symbols only when the target StringImpl* is unique and we need to
> > IncludeSymbols. So this performance impact only affects on
> > Object.getOwnPropertySymbols case.
> > What do you think of?
> 
> It's an interesting question.  I'd like to hear ggaren's thoughts on this. 
> I'm on the fence.  I like that your approach makes
> Object.getOwnPropertySymbols fast.  On the other hand, hash lookups can be
> made fast.  It's a tough call, so I'd like to see what others think.

My intuition is that there is value in separating the concern of this ES feature out of a low-level class like StringImpl.

Also, I think the cost of a hash lookup in gathering symbol names is probably acceptable because:

(1) name iteration already requires us to hash-unique names in the prototype chain;

(2) the most important optimization in making name iteration fast is to cache the result in the structure.

That said, if a benchmark showed this hash lookup to be a problem, I would want to find a better solution, and I would consider putting bits in StringImpl as an option.
Comment 33 Geoffrey Garen 2015-03-09 08:20:39 PDT
Comment on attachment 247933 [details]
Patch

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

> Source/JavaScriptCore/ChangeLog:12
> +        This patch implements `Object.getOwnPropertySymbols`.
> +        One technical issue is that, since we use private symbols (such as `@Object`) in the
> +        privileged JS code in `builtins/`, they should not be exposed.
> +        To distinguish them from the usual symbols, checking the target `StringImpl*` is a not private name
> +        before adding it into PropertyNameArray.

When is it possible to encounter a private symbol (like `@Object`) on an object?

I believe our implementation of builtins attempts to ensure that private symbols do not escape into any programmer-accessible namespace.

> Source/JavaScriptCore/ChangeLog:39
> +        + `Identifier::fromUid(VM*/ExecState*, StringImpl*)`.
> +            This function is used for 2) `uid`. So uniqueness of `StringImpl*` is kept.

Should we start saying "Symbol" now instead of "UID"?

> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:524
> +                    if (name->isUnique()) {
> +                        if (!mode.shouldIncludeSymbols())
> +                            continue;

This would read more clearly to me if the function on StringImpl were "isSymbol".

Is there a difference between a symbol, a unique string, and a uid? Perhaps I am showing my own ignorance here.

If there is no difference -- and I think there is none -- let's pick just one name for this concept. After all, sometimes one has to review a patch after waking up at 6AM, and one's poor little brain can use all the help it can get. :)

> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:526
> +                        if (exec->propertyNames().isPrivateName(name))
> +                            continue;

Perhaps this can just be an ASSERT? See my comment above about preventing private names from escaping.

> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:545
> +                    if (name->isUnique()) {
> +                        if (!mode.shouldIncludeSymbols())
> +                            continue;
> +                        if (exec->propertyNames().isPrivateName(name))
> +                            continue;
> +                    }

Ditto.
Comment 34 Geoffrey Garen 2015-03-09 08:26:37 PDT
> And one more possible solution is, folding private bit into StringImpl*
> itself.
> Like following code,
> 
> class PropertyName {

I think something like this might become desirable if the implementation of ES features related to property names adds even more features. For now, I think it might not be necessary to make such a large change.

For a bit of history on this subject, JavaScriptCore used to have its own string class, separate from StringImpl, and we worked hard to remove it, both for simplicity's sake, and to improve performance.

We could probably add back this PropertyName concept without harming performance -- but it would be nice not to have to.
Comment 35 Yusuke Suzuki 2015-03-09 08:52:18 PDT
(In reply to comment #32)
> My intuition is that there is value in separating the concern of this ES
> feature out of a low-level class like StringImpl.
> 
> Also, I think the cost of a hash lookup in gathering symbol names is
> probably acceptable because:
> 
> (1) name iteration already requires us to hash-unique names in the prototype
> chain;
> 
> (2) the most important optimization in making name iteration fast is to
> cache the result in the structure.
> 
> That said, if a benchmark showed this hash lookup to be a problem, I would
> want to find a better solution, and I would consider putting bits in
> StringImpl as an option.

Reasonable. OK, so, I'll take the way separating StringImpl and private bits.
Thank you :)
Comment 36 Yusuke Suzuki 2015-03-09 09:06:22 PDT
(In reply to comment #34)
> I think something like this might become desirable if the implementation of
> ES features related to property names adds even more features. For now, I
> think it might not be necessary to make such a large change.
> 
> For a bit of history on this subject, JavaScriptCore used to have its own
> string class, separate from StringImpl, and we worked hard to remove it,
> both for simplicity's sake, and to improve performance.
> 
> We could probably add back this PropertyName concept without harming
> performance -- but it would be nice not to have to.

Sounds reasonable.
Comment 37 Yusuke Suzuki 2015-03-09 09:54:13 PDT
Comment on attachment 247933 [details]
Patch

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

Added comments.

>> Source/JavaScriptCore/ChangeLog:12
>> +        before adding it into PropertyNameArray.
> 
> When is it possible to encounter a private symbol (like `@Object`) on an object?
> 
> I believe our implementation of builtins attempts to ensure that private symbols do not escape into any programmer-accessible namespace.

I think, after landing Object.getOwnPropertySymbols, we can encounter with the following code without guarding isPrivateName check.

var global = ...;  // like (new Function("return this"))()
var symbols = Object.getOwnPropertySymbols(global);

Correct?

>> Source/JavaScriptCore/ChangeLog:39
>> +            This function is used for 2) `uid`. So uniqueness of `StringImpl*` is kept.
> 
> Should we start saying "Symbol" now instead of "UID"?

Currently this Identifier::fromUid also accepts StringImpl* uid that represents normal string (which is used for the usual string (non-symbol) property name).
This is because in the current JSC implementation StringImpl* is held as <Symbol|StringProperty> type. So constructing Identifier for String/Symbol property from StringImpl* like Identifier::fromUid is useful.

>> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:524
>> +                            continue;
> 
> This would read more clearly to me if the function on StringImpl were "isSymbol".
> 
> Is there a difference between a symbol, a unique string, and a uid? Perhaps I am showing my own ignorance here.
> 
> If there is no difference -- and I think there is none -- let's pick just one name for this concept. After all, sometimes one has to review a patch after waking up at 6AM, and one's poor little brain can use all the help it can get. :)

Nice catch.
UID is different from Symbol beacuse it contains String property (It's <String|Symbol> id). But, isUnique is the same to isSymbol. So replacing isUnique with isSymbol makes the code clear.

>> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:526
>> +                            continue;
> 
> Perhaps this can just be an ASSERT? See my comment above about preventing private names from escaping.

Right. Here, since the staticValues' key is generated by String::fromUTF8(const char*), the string is not atomic & unique. So here, there's no symbol.
I'll insert `ASSER(!name->isSymbol());` here :)
Comment 38 Yusuke Suzuki 2015-03-09 12:07:28 PDT
Created attachment 248266 [details]
Patch
Comment 39 Yusuke Suzuki 2015-03-09 12:14:49 PDT
Comment on attachment 247933 [details]
Patch

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

>>> Source/JavaScriptCore/ChangeLog:39
>>> +            This function is used for 2) `uid`. So uniqueness of `StringImpl*` is kept.
>> 
>> Should we start saying "Symbol" now instead of "UID"?
> 
> Currently this Identifier::fromUid also accepts StringImpl* uid that represents normal string (which is used for the usual string (non-symbol) property name).
> This is because in the current JSC implementation StringImpl* is held as <Symbol|StringProperty> type. So constructing Identifier for String/Symbol property from StringImpl* like Identifier::fromUid is useful.

Calling StringImpl* as uid is derived from the fact that this type is actually stored in PropertyTable.
So, maybe, UID's *unique* comes from the meaning: "the uniqueness of Property name ID in PropertyTable".
Is it preferable to rename it to the other name? Personally, now I renamed `isUnique` to `isSymbol`, we can still use this "unique" meaning as "the uniqueness of Property name ID".
Comment 40 Geoffrey Garen 2015-03-09 13:03:29 PDT
> I think, after landing Object.getOwnPropertySymbols, we can encounter with
> the following code without guarding isPrivateName check.
> 
> var global = ...;  // like (new Function("return this"))()
> var symbols = Object.getOwnPropertySymbols(global);
> 
> Correct?

I see.

I thought private names were simply implemented as a feature in the parser, which produced a constant node in the AST. But I was wrong: Private names are properties of the global object.
Comment 41 Geoffrey Garen 2015-03-09 13:04:16 PDT
> Calling StringImpl* as uid is derived from the fact that this type is
> actually stored in PropertyTable.
> So, maybe, UID's *unique* comes from the meaning: "the uniqueness of
> Property name ID in PropertyTable".

Makes sense.
Comment 42 Yusuke Suzuki 2015-03-10 09:35:27 PDT
Comment on attachment 248266 [details]
Patch

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

OK, ready for review :)

> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:522
> +                    ASSERT(!name->isSymbol());

Here, `staticValues` key is created by String::fromUTF8 and copied by isolatedCopy in JSClassRef.cpp. So it should not be symbol.

> Source/JavaScriptCore/API/JSCallbackObjectFunctions.h:535
> +                    ASSERT(!name->isSymbol());

ditto
Comment 43 Timothy Hatcher 2015-03-13 10:46:53 PDT
Can we get this reviewed? It is blocking Web Inspector Symbol support in bug 141279.
Comment 44 Filip Pizlo 2015-03-13 14:30:44 PDT
Comment on attachment 248266 [details]
Patch

Let's go with the HashSet of private symbols approach.  It's less intrusive.
Comment 45 Yusuke Suzuki 2015-03-14 00:27:12 PDT
Created attachment 248642 [details]
Patch
Comment 46 Yusuke Suzuki 2015-03-14 00:33:48 PDT
Comment on attachment 248642 [details]
Patch

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

OK! I've updated the patch that takes non-intrusive way.

> Source/JavaScriptCore/builtins/BuiltinNames.h:95
> +    return m_privateToPublicMap.find(uid) != m_privateToPublicMap.end();

`BuiltinNames` already has `m_privateToPublicMap` to provide the function `BuiltinNames::getPrivateName(const Identifier& ident)`.
So all private symbols are already registered in this map :)
In this patch, we just leverage this existing map to query whether uid is private.

> Source/JavaScriptCore/runtime/JSLexicalEnvironment.cpp:124
> +                if (exec->propertyNames().isPrivateName(it->key.get()))

Use `isPrivateName` like this.
Private-ness is now held with non-intrusive way. HashMap in BuiltinNames (for getPrivateName function) manages all private symbols.
Comment 47 Yusuke Suzuki 2015-03-14 01:19:14 PDT
I'll rebaseline the patch with WebCore buildfix, but it's trivial.
Comment 48 Yusuke Suzuki 2015-03-14 06:51:24 PDT
Created attachment 248648 [details]
Patch
Comment 49 Yusuke Suzuki 2015-03-22 11:54:38 PDT
Simply rebasing...
Comment 50 Filip Pizlo 2015-03-22 12:04:52 PDT
This new approach involves doing a hash lookup to check if a name is private on most uses of property enumeration.  When we suggested this less-intrusive approach, we made that suggestion under the assumption that:

1) It would lead to a smaller patch.

2) That hash lookup cost would only be incurred when using the new Object.getOwnPropertySymbols API.

It appears that your implementation is a very large patch and we pay the hash lookup in a lot of places.

Why can't you change it so that the only place that consults the hashmap is Object.getOwnPropertySymbols?  That is, get rid of the new enumeration modes.  Don't change any of that code.  Don't change Structure, JSSymbolTableObject, etc.  Just put the filtering in Object.getOwnPropertySymbols.

Would that work?
Comment 51 Yusuke Suzuki 2015-03-22 12:53:43 PDT
(In reply to comment #50)
> This new approach involves doing a hash lookup to check if a name is private
> on most uses of property enumeration.  When we suggested this less-intrusive
> approach, we made that suggestion under the assumption that:
> 
> 1) It would lead to a smaller patch.
> 
> 2) That hash lookup cost would only be incurred when using the new
> Object.getOwnPropertySymbols API.
> 
> It appears that your implementation is a very large patch and we pay the
> hash lookup in a lot of places.
> 
> Why can't you change it so that the only place that consults the hashmap is
> Object.getOwnPropertySymbols?  That is, get rid of the new enumeration
> modes.  Don't change any of that code.  Don't change Structure,
> JSSymbolTableObject, etc.  Just put the filtering in
> Object.getOwnPropertySymbols.
> 
> Would that work?

OK :) As the result of discussion on IRC, I'll change this to

1. Exclude Symbols functionality is shared. Implemented in JSObject::getOwnPropertyNames JSC APIs
2. Filtering private symbols functionality is implemented in objectConstructorGetOwnPropertySymbols
Comment 52 Yusuke Suzuki 2015-03-22 13:36:32 PDT
Created attachment 249205 [details]
Patch
Comment 53 Yusuke Suzuki 2015-03-22 13:43:36 PDT
Comment on attachment 249205 [details]
Patch

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

Updated the patch.
Move hash lookups to ObjectConstructor.cpp.
After IRC discussion, I grep-ed tree and found several use of IncludeSymbols enumeration mode (Object.seal, Object.freeze, Object.defineProperties (Object.create), Object.isFrozen, Object.isSealed, Object.getOwnPropertySymbols)
However, all these use are placed in ObjectConstructor.cpp.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:233
> +        if (impl->isSymbol() && !exec->propertyNames().isPrivateName(impl))

Using hash lookup.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:380
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Using hash lookup for Object.defineProperties.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:430
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Using hash lookup for Object.seal.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:470
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Using hash lookup for Object.freeze.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:521
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Using hash lookup for Object.isSealed.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:554
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Using hash lookup for Object.isFrozen.
Comment 54 Yusuke Suzuki 2015-03-23 19:36:01 PDT
Could you take a look?
Comment 55 Yusuke Suzuki 2015-03-27 13:04:48 PDT
Comment on attachment 249205 [details]
Patch

Let's split it into several patches.
Comment 56 Yusuke Suzuki 2015-03-27 13:24:02 PDT
At first, I've split it into Identifier & StringImpl* changes.
https://bugs.webkit.org/show_bug.cgi?id=143146
Comment 57 Yusuke Suzuki 2015-03-31 15:08:07 PDT
Next https://bugs.webkit.org/show_bug.cgi?id=143276
Comment 58 Yusuke Suzuki 2015-04-02 19:59:29 PDT
Created attachment 250032 [details]
Patch
Comment 59 Yusuke Suzuki 2015-04-02 20:05:45 PDT
Now, splitted 2 patches are landed.
So Object.getOwnPropertySymbols patch becomes fairly small :D
Comment 60 Geoffrey Garen 2015-04-03 11:21:50 PDT
Comment on attachment 250032 [details]
Patch

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

r=me with some fixes outlined below.

> Source/JavaScriptCore/ChangeLog:11
> +        To distinguish them from the usual symbols, checking the target `StringImpl*` is a not private name

checking => check

> Source/JavaScriptCore/builtins/BuiltinNames.h:97
> +    return m_privateToPublicMap.find(uid) != m_privateToPublicMap.end();

You can use contains() here instead.

> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:383
> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))

Since isPrivateName checks isSymbol, we shouldn't check at the call site. It's an optimization, which is an implementation detail.

This pattern repeats in a few places.
Comment 61 Yusuke Suzuki 2015-04-04 07:29:55 PDT
Comment on attachment 250032 [details]
Patch

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

Thank you for your review!

>> Source/JavaScriptCore/ChangeLog:11
>> +        To distinguish them from the usual symbols, checking the target `StringImpl*` is a not private name
> 
> checking => check

Thanks!

>> Source/JavaScriptCore/builtins/BuiltinNames.h:97
>> +    return m_privateToPublicMap.find(uid) != m_privateToPublicMap.end();
> 
> You can use contains() here instead.

Looks very nice :D

>> Source/JavaScriptCore/runtime/ObjectConstructor.cpp:383
>> +        if (propertyName.isSymbol() && exec->propertyNames().isPrivateName(propertyName))
> 
> Since isPrivateName checks isSymbol, we shouldn't check at the call site. It's an optimization, which is an implementation detail.
> 
> This pattern repeats in a few places.

OK. I'll fix these guards.
Comment 62 Yusuke Suzuki 2015-04-04 07:33:48 PDT
Committed r182343: <http://trac.webkit.org/changeset/182343>