Closed Bug 1371591 Opened 7 years ago Closed 6 years ago

SetFunctionNameIfNoOwnName should try to avoid resolve and define operations

Categories

(Core :: JavaScript Engine, enhancement, P3)

enhancement

Tracking

()

RESOLVED FIXED
mozilla61
Tracking Status
firefox61 --- fixed

People

(Reporter: anba, Assigned: anba)

References

(Blocks 1 open bug)

Details

(Keywords: triage-deferred)

Attachments

(3 files, 2 obsolete files)

SetFunctionNameIfNoOwnName currently always calls the resolve-hook and defines a new slot for the "name" property. We should try to avoid both calls, maybe we can simply stash the function name in the existing JSFunction::atom_ field?


This µ-benchmark improved from 760ms to 130ms when I simply stored the function name in JSFunction::atom_:

    var t = Date.now();
    for (var i=0;i<1000000;++i) {
        var o = {["a"]: function (){}};
    }
    print(Date.now() - t);
Blocks: es6perf
Keywords: triage-deferred
Priority: -- → P3
First some clean-ups and simplifications:

Parser.cpp, SyntaxParseHandler.h, FullParseHandler.h, BytecodeEmitter.cpp:
- Only call checkAndSetIsDirectRHSAnonFunction() if the left-hand side is definitely an unparenthesised identifier. That way we don't need to check this condition in the BytecodeEmitter and it will simplify a change for part 3.

BytecodeEmitter::setOrEmitSetFunName()
- Add an extra assertion for clarity that this function should only be called if the function/class is marked as |isDirectRHSAnonFunction()|.

NameResolver::resolve():
- Add null-pointer checks for ParseNodeKind::Name and ParseNodeKind::Function, so we can remove the |cur == nullptr| check at the top of resolve(). All other callers already check for null-pointers before recursively calling resolve().
- Update the checks for ParseNodeKind::For{In,Of} (pn_kid1 is never a null-pointer, pn_kid2 is always a null-pointer).
- Update some comments and fix indentation.
Attachment #8960205 - Flags: review?(jorendorff)
- Remove a straight out lie in the IdToFunctionName() doc-comment.
- Change SetFunctionNameIfNoOwnName to skip the Value->Id conversion and instead directly construct the function name from a Symbol or Atom value.
- And replace the HasOwnProperty() call with |NativeObject::contains()|, so we don't run the resolve-handler for class constructors.
Attachment #8960212 - Flags: review?(jorendorff)
The actual change to store the function name in JSFunction::atom_ to avoid creating a "name" property:
- Replace |CompileTimeName| with |InferredName| in various methods/fields because we no longer only set the inferred name at compile-time.
- Remove the empty name assertion in JSFunction::getUnresolvedName(), because this case is now possible: |var o = {[""]: class {}};|.
- Remove HAS_COMPILE_TIME_NAME/HAS_INFERRED_NAME from JSFunction::STABLE_ACROSS_CLONES, because it may not be set in class constructor functions: |var c = class {static [randomOneOf("name", "no-name")](){}};|. (It's correct to remove it because of that case, right?)
- Don't assign a guessed function name in NameResolve::resolveFun() if we know that this function will get a (dynamically computed) inferred name at runtime.


And now most important:
Do you think this change is correct or is there some subtle reason why we can't save the function name in JSFunction::atom_ for dynamically computed names?
Attachment #8960220 - Flags: review?(jorendorff)
Comment on attachment 8960205 [details] [diff] [review]
bug1371591-part1-mark-anon-in-parser.patch

Review of attachment 8960205 [details] [diff] [review]:
-----------------------------------------------------------------

r=me and I sure hope these comments are helpful!

::: js/src/frontend/BytecodeEmitter.cpp
@@ +5488,1 @@
>      if (maybeFun->isKind(ParseNodeKind::Function)) {

The only other possibility is ParseNodeKind::Class, right? Would you mind asserting that after this if-block?

::: js/src/frontend/FullParseHandler.h
@@ +672,5 @@
>      }
>  
> +    bool isUnparenthesizedNameForRHSAnonFunction(ParseNode* pn) {
> +        return pn->isKind(ParseNodeKind::Name) && !pn->isInParens();
> +    }

If possible, please delete these two methods (checkAndSetIsDirectRHSAnonFunction and isUnparenthesizedNameForRHSAnonFunction) from the ParseHandler interface, and handle this case in FPH::newAssignment(), addPropertyDefinition(), and newExportDefaultDeclaration(). (FPH can have private methods for any shared code. I'm not worried about the extra branch per local variable declaration; if you are, add a method newAssignmentWithoutCheckingForSetFunctionName.)

Rationale: make the Parser code read better; make it harder for BinAST to get this bit wrong; eliminate the rather subtle "this boolean doesn't matter because that other method is a no-op" comment in SyntaxParseHandler; concentrate responsibility for this bit's correctness in one class (FPH).

The ParseHandler interface is already a bit of a dumping ground for complexity, and it's in constant danger of getting worse. To fight this, we should try to make it syntax-driven and applicative (in the functional-programming sense). In a perfect world, it would be nothing but `newFoo()` methods, one for each spec grammar production, with no setters at all.

::: js/src/frontend/NameFunctions.cpp
@@ +540,5 @@
>            case ParseNodeKind::Import:
>            case ParseNodeKind::ExportFrom:
>            case ParseNodeKind::ExportDefault:
>              MOZ_ASSERT(cur->isArity(PN_BINARY));
> +            // The left halves of Import and ExportFrom don't contain any

Heh. Now the comment makes the reader wonder, "But what about ExportDefault?"

I guess I would change it back, but it's up to you.
Attachment #8960205 - Flags: review?(jorendorff) → review+
Attachment #8960212 - Flags: review?(jorendorff) → review+
(In reply to Jason Orendorff [:jorendorff] from comment #4)
> ::: js/src/frontend/NameFunctions.cpp
> @@ +540,5 @@
> >            case ParseNodeKind::Import:
> >            case ParseNodeKind::ExportFrom:
> >            case ParseNodeKind::ExportDefault:
> >              MOZ_ASSERT(cur->isArity(PN_BINARY));
> > +            // The left halves of Import and ExportFrom don't contain any
> 
> Heh. Now the comment makes the reader wonder, "But what about ExportDefault?"
> 
> I guess I would change it back, but it's up to you.

ExportDefault can contain arbitrary expressions (for the "export default AssignExpression" form). That's why I wanted to exclude it from the "[...] don't contain any unconstrained expressions [...]" comment.
Comment on attachment 8960220 [details] [diff] [review]
bug1371591-part3-store-atom.patch

Review of attachment 8960220 [details] [diff] [review]:
-----------------------------------------------------------------

Yes, I think this change is correct.

Function names are a mess, before and after this patch. :-( It seems sloppy that we use explicitName() in so many case and displayAtom() in so many others, generally with no indication of whether it matters at all which one we're using. I wonder if we have bugs where displayAtom() is used on bound functions, producing different results depending on whether getUnresolvedName() has been called. Or other stuff like that.

::: js/src/vm/JSFunction.cpp
@@ +1332,3 @@
>          // Unnamed class expressions should not get a .name property at all.
>          if (name)
>              v.set(name);

The `if (name)` test is unnecessary. If you agree, please remove it, and set v unconditionally.
Attachment #8960220 - Flags: review?(jorendorff) → review+
Assignee: nobody → andrebargull
Status: NEW → ASSIGNED
(In reply to Jason Orendorff [:jorendorff] from comment #6)
> Comment on attachment 8960220 [details] [diff] [review]
> bug1371591-part3-store-atom.patch
> 
> Review of attachment 8960220 [details] [diff] [review]:
> -----------------------------------------------------------------
> 
> Yes, I think this change is correct.
> 
> Function names are a mess, before and after this patch. :-( It seems sloppy
> that we use explicitName() in so many case and displayAtom() in so many
> others, generally with no indication of whether it matters at all which one
> we're using. I wonder if we have bugs where displayAtom() is used on bound
> functions, producing different results depending on whether
> getUnresolvedName() has been called. Or other stuff like that.

Yeah, bug 1371593 and the new bug 1447362 both made displayAtom() for bound functions a bit unreliable. But even before that displayAtom() no longer gave consistent results, for example because of syntax parsing:
---
function testDisplayName(directive) {
    eval(`
    function outer() {
        "${directive}";
        function inner() {
            return function(){};
        }
        print(displayName(inner()));
    }
    outer();
    `);
}

// Prints "inner/<" with syntax parsing enabled.
testDisplayName();

// Prints "outer/inner/<" with syntax parsing disabled.
testDisplayName("use asm");
---

And nobody seems to care. :-)


> ::: js/src/vm/JSFunction.cpp
> @@ +1332,3 @@
> >          // Unnamed class expressions should not get a .name property at all.
> >          if (name)
> >              v.set(name);
> 
> The `if (name)` test is unnecessary. If you agree, please remove it, and set
> v unconditionally.

bug 1447362 removes this test.
(In reply to Jason Orendorff [:jorendorff] from comment #4)
> If possible, please delete these two methods
> (checkAndSetIsDirectRHSAnonFunction and
> isUnparenthesizedNameForRHSAnonFunction) from the ParseHandler interface,
> and handle this case in FPH::newAssignment(), addPropertyDefinition(), and
> newExportDefaultDeclaration(). (FPH can have private methods for any shared
> code. I'm not worried about the extra branch per local variable declaration;
> if you are, add a method newAssignmentWithoutCheckingForSetFunctionName.)

FPH::newAssignment() and FPH::newExportDefaultDeclaration() are easy to change, but FPH::addPropertyDefinition() is a tricky one, because we need to check for anonymous functions before calling FoldConstants [2], but also want to call FoldConstants before marking the object literal as non-constant [2] resp. calling addPropertyDefinition().


[1] https://searchfox.org/mozilla-central/rev/877c99c523a054419ec964d4dfb3f0cadac9d497/js/src/frontend/Parser.cpp#9536-9537
[2] https://searchfox.org/mozilla-central/rev/877c99c523a054419ec964d4dfb3f0cadac9d497/js/src/frontend/Parser.cpp#9562-9563
Updated per review comments, carrying r+.
Attachment #8960205 - Attachment is obsolete: true
Attachment #8960958 - Flags: review+
Update part 3 to apply on updated part 1.
Attachment #8960220 - Attachment is obsolete: true
Attachment #8960960 - Flags: review+
Pushed by ryanvm@gmail.com:
https://hg.mozilla.org/integration/mozilla-inbound/rev/068989e6ca33
Part 1: Move parenthesized identifier check to parser. r=jorendorff
https://hg.mozilla.org/integration/mozilla-inbound/rev/c5be9e57bbef
Part 2: Remove extra calls in SetFunctionNameIfNoOwnName. r=jorendorff
https://hg.mozilla.org/integration/mozilla-inbound/rev/312e1b24fca5
Part 3: Store dynamic function names into the name atom instead of a property. r=jorendorff
Keywords: checkin-needed
Regressions: 1448582
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: