Bug 883377 - Part 1: Implement ES6 function name property semantics. r=jandem,anba
authorTooru Fujisawa <arai_a@mac.com>
Sat, 03 Dec 2016 07:44:20 +0900
changeset 325183 0faba97ebe86d2e32c6afee3f0486cb5b538d74f
parent 325182 8b922bd2f864d6c850f5624f26775348becd2365
child 325184 c94153a390fa65444bf578aca5e3a562a170ebce
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersjandem, anba
bugs883377
milestone53.0a1
Bug 883377 - Part 1: Implement ES6 function name property semantics. r=jandem,anba
js/src/builtin/ModuleObject.cpp
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode-inl.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/SyntaxParseHandler.h
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsfuninlines.h
js/src/tests/ecma_6/Function/function-name-assignment.js
js/src/tests/ecma_6/Function/function-name-binding.js
js/src/tests/ecma_6/Function/function-name-class.js
js/src/tests/ecma_6/Function/function-name-for.js
js/src/tests/ecma_6/Function/function-name-method.js
js/src/tests/ecma_6/Function/function-name-property.js
js/src/vm/AsyncFunction.cpp
js/src/vm/Debugger.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/SelfHosting.cpp
js/src/vm/TypeInference.cpp
js/src/vm/TypedArrayObject.cpp
js/src/wasm/AsmJS.cpp
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1200,17 +1200,17 @@ ModuleBuilder::processExport(frontend::P
                   return false;
           }
           break;
       }
 
       case PNK_FUNCTION: {
           RootedFunction func(cx_, kid->pn_funbox->function());
           if (!func->isArrow()) {
-              RootedAtom localName(cx_, func->name());
+              RootedAtom localName(cx_, func->explicitName());
               RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
               MOZ_ASSERT_IF(isDefault, localName);
               if (!appendExportEntry(exportName, localName))
                   return false;
               break;
           }
       }
 
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -3405,17 +3405,17 @@ ASTSerializer::function(ParseNode* pn, A
            ? GeneratorStyle::Legacy
            : GeneratorStyle::ES6)
         : GeneratorStyle::None;
 
     bool isAsync = pn->pn_funbox->isAsync();
     bool isExpression = pn->pn_funbox->isExprBody();
 
     RootedValue id(cx);
-    RootedAtom funcAtom(cx, func->name());
+    RootedAtom funcAtom(cx, func->explicitName());
     if (!optIdentifier(funcAtom, nullptr, &id))
         return false;
 
     NodeVector args(cx);
     NodeVector defaults(cx);
 
     RootedValue body(cx), rest(cx);
     if (pn->pn_funbox->hasRest())
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4067,17 +4067,17 @@ BytecodeEmitter::isRunOnceLambda()
         (emitterMode != LazyFunction || !lazyScript->treatAsRunOnce()))
     {
         return false;
     }
 
     FunctionBox* funbox = sc->asFunctionBox();
     return !funbox->argumentsHasLocalBinding() &&
            !funbox->isGenerator() &&
-           !funbox->function()->name();
+           !funbox->function()->explicitName();
 }
 
 bool
 BytecodeEmitter::emitYieldOp(JSOp op)
 {
     if (op == JSOP_FINALYIELDRVAL)
         return emit1(JSOP_FINALYIELDRVAL);
 
@@ -4488,39 +4488,105 @@ BytecodeEmitter::emitIteratorNext(ParseN
         return false;
     if (!emitCheckIsObj(CheckIsObjectKind::IteratorNext)) // ... RESULT
         return false;
     checkTypeSet(JSOP_CALL);
     return true;
 }
 
 bool
-BytecodeEmitter::emitDefault(ParseNode* defaultExpr)
+BytecodeEmitter::emitDefault(ParseNode* defaultExpr, ParseNode* pattern)
 {
     if (!emit1(JSOP_DUP))                                 // VALUE VALUE
         return false;
     if (!emit1(JSOP_UNDEFINED))                           // VALUE VALUE UNDEFINED
         return false;
     if (!emit1(JSOP_STRICTEQ))                            // VALUE EQL?
         return false;
     // Emit source note to enable ion compilation.
     if (!newSrcNote(SRC_IF))
         return false;
     JumpList jump;
     if (!emitJump(JSOP_IFEQ, &jump))                      // VALUE
         return false;
     if (!emit1(JSOP_POP))                                 // .
         return false;
-    if (!emitTreeInBranch(defaultExpr))                   // DEFAULTVALUE
+    if (!emitInitializerInBranch(defaultExpr, pattern))   // DEFAULTVALUE
         return false;
     if (!emitJumpTargetAndPatch(jump))
         return false;
     return true;
 }
 
+bool
+BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name,
+                                     FunctionPrefixKind prefixKind)
+{
+    if (maybeFun->isKind(PNK_FUNCTION)) {
+        // Function doesn't have 'name' property at this point.
+        // Set function's name at compile time.
+        RootedFunction fun(cx, maybeFun->pn_funbox->function());
+
+        // Single node can be emitted multiple times if it appears in
+        // array destructuring default.  If function already has a name,
+        // just return.
+        if (fun->hasCompileTimeName()) {
+#ifdef DEBUG
+            RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind));
+            if (!funName)
+                return false;
+            MOZ_ASSERT(funName == maybeFun->pn_funbox->function()->compileTimeName());
+#endif
+            return true;
+        }
+
+        RootedAtom funName(cx, NameToFunctionName(cx, name, prefixKind));
+        if (!funName)
+            return false;
+        if (fun->hasGuessedAtom())
+            fun->clearGuessedAtom();
+        fun->setCompileTimeName(name);
+        return true;
+    }
+
+    uint32_t nameIndex;
+    if (!makeAtomIndex(name, &nameIndex))
+        return false;
+    if (!emitIndexOp(JSOP_STRING, nameIndex))   // FUN NAME
+        return false;
+    uint8_t kind = uint8_t(prefixKind);
+    if (!emit2(JSOP_SETFUNNAME, kind))          // FUN
+        return false;
+    return true;
+}
+
+bool
+BytecodeEmitter::emitInitializer(ParseNode* initializer, ParseNode* pattern)
+{
+    if (!emitTree(initializer))
+        return false;
+
+    if (!pattern->isInParens() && pattern->isKind(PNK_NAME) &&
+        initializer->isDirectRHSAnonFunction())
+    {
+        RootedAtom name(cx, pattern->name());
+        if (!setOrEmitSetFunName(initializer, name, FunctionPrefixKind::None))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern)
+{
+    TDZCheckCache tdzCache(this);
+    return emitInitializer(initializer, pattern);
+}
+
 class MOZ_STACK_CLASS IfThenElseEmitter
 {
     BytecodeEmitter* bce_;
     JumpList jumpAroundThen_;
     JumpList jumpsAroundElse_;
     unsigned noteIndex_;
     int32_t thenDepth_;
 #ifdef DEBUG
@@ -4831,17 +4897,17 @@ BytecodeEmitter::emitDestructuringOpsArr
         if (!ifThenElse.emitIfElse())                             // ... OBJ? ITER RESULT
             return false;
 
         if (!emit1(JSOP_POP))                                     // ... OBJ? ITER
             return false;
         if (pndefault) {
             // Emit only pndefault tree here, as undefined check in emitDefault
             // should always be true.
-            if (!emitTreeInBranch(pndefault))                     // ... OBJ? ITER VALUE
+            if (!emitInitializerInBranch(pndefault, subpattern))  // ... OBJ? ITER VALUE
                 return false;
         } else {
             if (!isElision) {
                 if (!emit1(JSOP_UNDEFINED))                       // ... OBJ? ITER UNDEFINED
                     return false;
                 if (!emit1(JSOP_NOP_DESTRUCTURING))
                     return false;
             }
@@ -4869,17 +4935,17 @@ BytecodeEmitter::emitDestructuringOpsArr
 
         if (!ifThenElse.emitElse())                               // ... OBJ? ITER RESULT
             return false;
 
         if (!emitAtomOp(cx->names().value, JSOP_GETPROP))         // ... OBJ? ITER VALUE
             return false;
 
         if (pndefault) {
-            if (!emitDefault(pndefault))                          // ... OBJ? ITER VALUE
+            if (!emitDefault(pndefault, subpattern))              // ... OBJ? ITER VALUE
                 return false;
         }
 
         if (!isElision) {
             if (!emitDestructuringLHSInBranch(subpattern, flav))  // ... OBJ? ITER
                 return false;
         } else {
             if (!emit1(JSOP_POP))                                 // ... OBJ? ITER
@@ -4977,17 +5043,17 @@ BytecodeEmitter::emitDestructuringOpsObj
             subpattern = member->pn_right;
         }
 
         // Get the property value if not done already.
         if (needsGetElem && !emitElemOpBase(JSOP_GETELEM))        // ... RHS PROP
             return false;
 
         if (subpattern->isKind(PNK_ASSIGN)) {
-            if (!emitDefault(subpattern->pn_right))
+            if (!emitDefault(subpattern->pn_right, subpattern->pn_left))
                 return false;
             subpattern = subpattern->pn_left;
         }
 
         // Destructure PROP per this member's subpattern.
         if (!emitDestructuringLHS(subpattern, flav))
             return false;
     }
@@ -5091,28 +5157,28 @@ BytecodeEmitter::emitSingleDeclaration(P
                                        ParseNode* initializer)
 {
     MOZ_ASSERT(decl->isKind(PNK_NAME));
 
     // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
     if (!initializer && declList->isKind(PNK_VAR))
         return true;
 
-    auto emitRhs = [initializer, declList](BytecodeEmitter* bce, const NameLocation&, bool) {
+    auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) {
         if (!initializer) {
             // Lexical declarations are initialized to undefined without an
             // initializer.
             MOZ_ASSERT(declList->isKind(PNK_LET),
                        "var declarations without initializers handled above, "
                        "and const declarations must have initializers");
             return bce->emit1(JSOP_UNDEFINED);
         }
 
         MOZ_ASSERT(initializer);
-        return bce->emitTree(initializer);
+        return bce->emitInitializer(initializer, decl);
     };
 
     if (!emitInitializeName(decl, emitRhs))
         return false;
 
     // Pop the RHS.
     return emit1(JSOP_POP);
 }
@@ -5161,16 +5227,22 @@ BytecodeEmitter::emitAssignment(ParseNod
                 }
             }
 
             // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
             // the top of the stack and we need to pick the right RHS value.
             if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1))
                 return false;
 
+            if (!lhs->isInParens() && op == JSOP_NOP && rhs && rhs->isDirectRHSAnonFunction()) {
+                RootedAtom name(bce->cx, lhs->name());
+                if (!bce->setOrEmitSetFunName(rhs, name, FunctionPrefixKind::None))
+                    return false;
+            }
+
             // Emit the compound assignment op if there is one.
             if (op != JSOP_NOP && !bce->emit1(op))
                 return false;
 
             return true;
         };
 
         return emitSetName(lhs, emitRhs);
@@ -6280,18 +6352,18 @@ BytecodeEmitter::emitForIn(ParseNode* fo
         if (decl->isKind(PNK_NAME)) {
             if (ParseNode* initializer = decl->expr()) {
                 MOZ_ASSERT(forInTarget->isKind(PNK_VAR),
                            "for-in initializers are only permitted for |var| declarations");
 
                 if (!updateSourceCoordNotes(decl->pn_pos.begin))
                     return false;
 
-                auto emitRhs = [initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
-                    return bce->emitTree(initializer);
+                auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
+                    return bce->emitInitializer(initializer, decl);
                 };
 
                 if (!emitInitializeName(decl, emitRhs))
                     return false;
 
                 // Pop the initializer.
                 if (!emit1(JSOP_POP))
                     return false;
@@ -6900,17 +6972,17 @@ BytecodeEmitter::emitComprehensionFor(Pa
            : emitComprehensionForOf(compFor);
 }
 
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
-    RootedAtom name(cx, fun->name());
+    RootedAtom name(cx, fun->explicitName());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
     MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto);
 
     /*
      * Set the |wasEmitted| flag in the funbox once the function has been
      * emitted. Function definitions that need hoisting to the top of the
      * function will be seen by emitFunction in two places.
      */
@@ -8529,16 +8601,20 @@ BytecodeEmitter::emitPropertyList(ParseN
         if (!emitTree(propdef->pn_right))
             return false;
 
         JSOp op = propdef->getOp();
         MOZ_ASSERT(op == JSOP_INITPROP ||
                    op == JSOP_INITPROP_GETTER ||
                    op == JSOP_INITPROP_SETTER);
 
+        FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER ? FunctionPrefixKind::Get
+                                        : op == JSOP_INITPROP_SETTER ? FunctionPrefixKind::Set
+                                        : FunctionPrefixKind::None;
+
         if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
             objp.set(nullptr);
 
         if (propdef->pn_right->isKind(PNK_FUNCTION) &&
             propdef->pn_right->pn_funbox->needsHomeObject())
         {
             MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty());
             bool isAsync = propdef->pn_right->pn_funbox->isAsync();
@@ -8570,16 +8646,22 @@ BytecodeEmitter::emitPropertyList(ParseN
               case JSOP_INITPROP:               op = JSOP_INITELEM;              break;
               case JSOP_INITHIDDENPROP:         op = JSOP_INITHIDDENELEM;        break;
               case JSOP_INITPROP_GETTER:        op = JSOP_INITELEM_GETTER;       break;
               case JSOP_INITHIDDENPROP_GETTER:  op = JSOP_INITHIDDENELEM_GETTER; break;
               case JSOP_INITPROP_SETTER:        op = JSOP_INITELEM_SETTER;       break;
               case JSOP_INITHIDDENPROP_SETTER:  op = JSOP_INITHIDDENELEM_SETTER; break;
               default: MOZ_CRASH("Invalid op");
             }
+            if (propdef->pn_right->isDirectRHSAnonFunction()) {
+                if (!emitDupAt(1))
+                    return false;
+                if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind)))
+                    return false;
+            }
             if (!emit1(op))
                 return false;
         } else {
             MOZ_ASSERT(key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING));
 
             uint32_t index;
             if (!makeAtomIndex(key->pn_atom, &index))
                 return false;
@@ -8594,16 +8676,21 @@ BytecodeEmitter::emitPropertyList(ParseN
                                           JSPROP_ENUMERATE))
                 {
                     return false;
                 }
                 if (objp->inDictionaryMode())
                     objp.set(nullptr);
             }
 
+            if (propdef->pn_right->isDirectRHSAnonFunction()) {
+                RootedAtom keyName(cx, key->pn_atom);
+                if (!setOrEmitSetFunName(propdef->pn_right, keyName, prefixKind))
+                    return false;
+            }
             if (!emitIndex32(op, index))
                 return false;
         }
 
         if (extraPop) {
             if (!emit1(JSOP_POP))
                 return false;
         }
@@ -9014,17 +9101,17 @@ BytecodeEmitter::emitFunctionFormalParam
             // Emit source note to enable Ion compilation.
             if (!newSrcNote(SRC_IF))
                 return false;
             JumpList jump;
             if (!emitJump(JSOP_IFEQ, &jump))
                 return false;
             if (!emit1(JSOP_POP))
                 return false;
-            if (!emitTreeInBranch(initializer))
+            if (!emitInitializerInBranch(initializer, bindingElement))
                 return false;
             if (!emitJumpTargetAndPatch(jump))
                 return false;
         } else if (isRest) {
             if (!emit1(JSOP_REST))
                 return false;
             checkTypeSet(JSOP_REST);
         }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -672,17 +672,26 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitIterator();
 
     // Pops iterator from the top of the stack. Pushes the result of |.next()|
     // onto the stack.
     MOZ_MUST_USE bool emitIteratorNext(ParseNode* pn, bool allowSelfHosted = false);
 
     // Check if the value on top of the stack is "undefined". If so, replace
     // that value on the stack with the value defined by |defaultExpr|.
-    MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr);
+    // |pattern| is a lhs node of the default expression.  If it's an
+    // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
+    // is called at compile time.
+    MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
+
+    MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name,
+                                          FunctionPrefixKind prefixKind);
+
+    MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
+    MOZ_MUST_USE bool emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern);
 
     MOZ_MUST_USE bool emitCallSiteObject(ParseNode* pn);
     MOZ_MUST_USE bool emitTemplateString(ParseNode* pn);
     MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp op, ParseNode* rhs);
 
     MOZ_MUST_USE bool emitReturn(ParseNode* pn);
     MOZ_MUST_USE bool emitStatement(ParseNode* pn);
     MOZ_MUST_USE bool emitStatementList(ParseNode* pn);
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -662,16 +662,21 @@ class FullParseHandler
     inline MOZ_MUST_USE bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
                                            ParseNode* catchName, ParseNode* catchGuard,
                                            ParseNode* catchBody);
 
     inline MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(ParseNode* funcpn,
                                                                    ParseNode* pn);
     inline void setLastFunctionFormalParameterDestructuring(ParseNode* funcpn, ParseNode* pn);
 
+    void checkAndSetIsDirectRHSAnonFunction(ParseNode* pn) {
+        if (IsAnonymousFunctionDefinition(pn))
+            pn->setDirectRHSAnonFunction(true);
+    }
+
     ParseNode* newFunctionStatement() {
         return new_<CodeNode>(PNK_FUNCTION, JSOP_NOP, pos());
     }
 
     ParseNode* newFunctionExpression() {
         return new_<CodeNode>(PNK_FUNCTION, JSOP_LAMBDA, pos());
     }
 
@@ -944,16 +949,18 @@ FullParseHandler::addCatchBlock(ParseNod
 inline bool
 FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn, ParseNode* defaultValue)
 {
     ParseNode* arg = funcpn->pn_body->last();
     ParseNode* pn = newBinary(PNK_ASSIGN, arg, defaultValue, JSOP_NOP);
     if (!pn)
         return false;
 
+    checkAndSetIsDirectRHSAnonFunction(defaultValue);
+
     funcpn->pn_body->pn_pos.end = pn->pn_pos.end;
     ParseNode* pnchild = funcpn->pn_body->pn_head;
     ParseNode* pnlast = funcpn->pn_body->last();
     MOZ_ASSERT(pnchild);
     if (pnchild == pnlast) {
         funcpn->pn_body->pn_head = pn;
     } else {
         while (pnchild->pn_next != pnlast) {
--- a/js/src/frontend/ParseNode-inl.h
+++ b/js/src/frontend/ParseNode-inl.h
@@ -13,17 +13,17 @@
 
 namespace js {
 namespace frontend {
 
 inline PropertyName*
 ParseNode::name() const
 {
     MOZ_ASSERT(isKind(PNK_FUNCTION) || isKind(PNK_NAME));
-    JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->name() : pn_atom;
+    JSAtom* atom = isKind(PNK_FUNCTION) ? pn_funbox->function()->explicitName() : pn_atom;
     return atom->asPropertyName();
 }
 
 inline JSAtom*
 ParseNode::atom() const
 {
     MOZ_ASSERT(isKind(PNK_STRING));
     return pn_atom;
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -897,8 +897,26 @@ ObjectBox::trace(JSTracer* trc)
 
 void
 FunctionBox::trace(JSTracer* trc)
 {
     ObjectBox::trace(trc);
     if (enclosingScope_)
         TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope");
 }
+
+bool
+js::frontend::IsAnonymousFunctionDefinition(ParseNode* pn)
+{
+    // ES 2017 draft
+    // 12.15.2 (ArrowFunction, AsyncArrowFunction).
+    // 14.1.12 (FunctionExpression).
+    // 14.4.8 (GeneratorExpression).
+    // 14.6.8 (AsyncFunctionExpression)
+    if (pn->isKind(PNK_FUNCTION) && !pn->pn_funbox->function()->explicitName())
+        return true;
+
+    // 14.5.8 (ClassExpression)
+    if (pn->is<ClassNode>() && !pn->as<ClassNode>().names())
+        return true;
+
+    return false;
+}
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -445,38 +445,43 @@ class ConditionalExpression;
 class PropertyAccess;
 
 class ParseNode
 {
     uint16_t pn_type;   /* PNK_* type */
     uint8_t pn_op;      /* see JSOp enum and jsopcode.tbl */
     uint8_t pn_arity:4; /* see ParseNodeArity enum */
     bool pn_parens:1;   /* this expr was enclosed in parens */
+    bool pn_rhs_anon_fun:1;  /* this expr is anonymous function or class that
+                              * is a direct RHS of PNK_ASSIGN or PNK_COLON of
+                              * property, that needs SetFunctionName. */
 
     ParseNode(const ParseNode& other) = delete;
     void operator=(const ParseNode& other) = delete;
 
   public:
     ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity)
       : pn_type(kind),
         pn_op(op),
         pn_arity(arity),
         pn_parens(false),
+        pn_rhs_anon_fun(false),
         pn_pos(0, 0),
         pn_next(nullptr)
     {
         MOZ_ASSERT(kind < PNK_LIMIT);
         memset(&pn_u, 0, sizeof pn_u);
     }
 
     ParseNode(ParseNodeKind kind, JSOp op, ParseNodeArity arity, const TokenPos& pos)
       : pn_type(kind),
         pn_op(op),
         pn_arity(arity),
         pn_parens(false),
+        pn_rhs_anon_fun(false),
         pn_pos(pos),
         pn_next(nullptr)
     {
         MOZ_ASSERT(kind < PNK_LIMIT);
         memset(&pn_u, 0, sizeof pn_u);
     }
 
     JSOp getOp() const                     { return JSOp(pn_op); }
@@ -507,16 +512,23 @@ class ParseNode
         return PNK_BINOP_FIRST <= kind && kind <= PNK_BINOP_LAST;
     }
 
     /* Boolean attributes. */
     bool isInParens() const                { return pn_parens; }
     bool isLikelyIIFE() const              { return isInParens(); }
     void setInParens(bool enabled)         { pn_parens = enabled; }
 
+    bool isDirectRHSAnonFunction() const {
+        return pn_rhs_anon_fun;
+    }
+    void setDirectRHSAnonFunction(bool enabled) {
+        pn_rhs_anon_fun = enabled;
+    }
+
     TokenPos            pn_pos;         /* two 16-bit pairs here, for 64 bits */
     ParseNode*          pn_next;        /* intrinsic link in parent PN_LIST */
 
     union {
         struct {                        /* list of next-linked nodes */
             ParseNode*  head;           /* first node in list */
             ParseNode** tail;           /* ptr to ptr to last node in list */
             uint32_t    count;          /* number of nodes in list */
@@ -1443,12 +1455,15 @@ FunctionFormalParametersList(ParseNode* 
         argsBody->last()->scopeBody()->isKind(PNK_STATEMENTLIST))
     {
         (*numFormals)--;
     }
     MOZ_ASSERT(argsBody->isArity(PN_LIST));
     return argsBody->pn_head;
 }
 
+bool
+IsAnonymousFunctionDefinition(ParseNode* pn);
+
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_ParseNode_h */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -321,20 +321,24 @@ ParseContext::init()
         // Named lambdas always need a binding for their own name. If this
         // binding is closed over when we finish parsing the function in
         // finishExtraFunctionScopes, the function box needs to be marked as
         // needing a dynamic DeclEnv object.
         RootedFunction fun(cx, functionBox()->function());
         if (fun->isNamedLambda()) {
             if (!namedLambdaScope_->init(this))
                 return false;
-            AddDeclaredNamePtr p = namedLambdaScope_->lookupDeclaredNameForAdd(fun->name());
+            AddDeclaredNamePtr p =
+                namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName());
             MOZ_ASSERT(!p);
-            if (!namedLambdaScope_->addDeclaredName(this, p, fun->name(), DeclarationKind::Const))
+            if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(),
+                                                    DeclarationKind::Const))
+            {
                 return false;
+            }
         }
 
         if (!functionScope_->init(this))
             return false;
 
         if (!positionalFormalParameterNames_.acquire(cx))
             return false;
     }
@@ -362,17 +366,17 @@ ParseContext::addInnerFunctionBoxForAnne
     return innerFunctionBoxesForAnnexB_->append(funbox);
 }
 
 void
 ParseContext::removeInnerFunctionBoxesForAnnexB(JSAtom* name)
 {
     for (uint32_t i = 0; i < innerFunctionBoxesForAnnexB_->length(); i++) {
         if (FunctionBox* funbox = innerFunctionBoxesForAnnexB_[i]) {
-            if (funbox->function()->name() == name)
+            if (funbox->function()->explicitName() == name)
                 innerFunctionBoxesForAnnexB_[i] = nullptr;
         }
     }
 }
 
 void
 ParseContext::finishInnerFunctionBoxesForAnnexB()
 {
@@ -3462,18 +3466,18 @@ Parser<ParseHandler>::functionFormalPara
     // |yield| in the parameters is either a name or keyword, depending on
     // whether the arrow function is enclosed in a generator function or not.
     // Whereas the |yield| in the function body is always parsed as a name.
     YieldHandling bodyYieldHandling = GetYieldHandling(pc->generatorKind(), pc->asyncKind());
     Node body = functionBody(inHandling, bodyYieldHandling, kind, bodyType);
     if (!body)
         return false;
 
-    if ((kind != Method && !IsConstructorKind(kind)) && fun->name()) {
-        RootedPropertyName propertyName(context, fun->name()->asPropertyName());
+    if ((kind != Method && !IsConstructorKind(kind)) && fun->explicitName()) {
+        RootedPropertyName propertyName(context, fun->explicitName()->asPropertyName());
         if (!checkStrictBinding(propertyName, handler.getPosition(pn)))
             return false;
     }
 
     if (bodyType == StatementListBody) {
         bool matched;
         if (!tokenStream.matchToken(&matched, TOK_RC, TokenStream::Operand))
             return false;
@@ -4332,16 +4336,18 @@ Parser<ParseHandler>::declarationPattern
 
     MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
 
     Node init = assignExpr(forHeadKind ? InProhibited : InAllowed,
                            yieldHandling, TripledotProhibited);
     if (!init)
         return null();
 
+    handler.checkAndSetIsDirectRHSAnonFunction(init);
+
     if (forHeadKind) {
         // For for(;;) declarations, consistency with |for (;| parsing requires
         // that the ';' first be examined as Operand, even though absence of a
         // binary operator (examined with modifier None) terminated |init|.
         // For all other declarations, through ASI's infinite majesty, a next
         // token on a new line would begin an expression.
         tokenStream.addModifierException(TokenStream::OperandIsNone);
     }
@@ -4365,16 +4371,18 @@ Parser<ParseHandler>::initializerInNameD
     if (!tokenStream.peekOffset(&initializerOffset, TokenStream::Operand))
         return false;
 
     Node initializer = assignExpr(forHeadKind ? InProhibited : InAllowed,
                                   yieldHandling, TripledotProhibited);
     if (!initializer)
         return false;
 
+    handler.checkAndSetIsDirectRHSAnonFunction(initializer);
+
     if (forHeadKind) {
         if (initialDeclaration) {
             bool isForIn, isForOf;
             if (!matchInOrOf(&isForIn, &isForOf))
                 return false;
 
             // An initialized declaration can't appear in a for-of:
             //
@@ -5053,17 +5061,17 @@ Parser<FullParseHandler>::exportDeclarat
 
       }
 
       case TOK_FUNCTION:
         kid = functionStmt(YieldIsKeyword, NameRequired);
         if (!kid)
             return null();
 
-        if (!checkExportedName(kid->pn_funbox->function()->name()))
+        if (!checkExportedName(kid->pn_funbox->function()->explicitName()))
             return null();
         break;
 
       case TOK_CLASS: {
         kid = classDefinition(YieldIsKeyword, ClassStatement, NameRequired);
         if (!kid)
             return null();
 
@@ -6664,18 +6672,16 @@ Parser<ParseHandler>::classDefinition(Yi
             }
             seenConstructor = true;
             propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor;
         } else if (isStatic && propAtom == context->names().prototype) {
             errorAt(nameOffset, JSMSG_BAD_METHOD_DEF);
             return null();
         }
 
-        // FIXME: Implement ES6 function "name" property semantics
-        // (bug 883377).
         RootedAtom funName(context);
         switch (propType) {
           case PropertyType::GetterNoExpressionClosure:
           case PropertyType::SetterNoExpressionClosure:
             if (!tokenStream.isCurrentTokenType(TOK_RB)) {
                 funName = prefixAccessorName(propType, propAtom);
                 if (!funName)
                     return null();
@@ -6688,16 +6694,18 @@ Parser<ParseHandler>::classDefinition(Yi
           default:
             if (!tokenStream.isCurrentTokenType(TOK_RB))
                 funName = propAtom;
         }
         Node fn = methodDefinition(propType, funName);
         if (!fn)
             return null();
 
+        handler.checkAndSetIsDirectRHSAnonFunction(fn);
+
         JSOp op = JSOpFromPropertyType(propType);
         if (!handler.addClassMethodDefinition(classMethods, propName, fn, op, isStatic))
             return null();
     }
 
     Node nameNode = null();
     Node methodsOrBlock = classMethods;
     if (name) {
@@ -7742,16 +7750,19 @@ Parser<ParseHandler>::assignExpr(InHandl
     Node rhs;
     {
         AutoClearInDestructuringDecl autoClear(pc);
         rhs = assignExpr(inHandling, yieldHandling, TripledotProhibited);
         if (!rhs)
             return null();
     }
 
+    if (kind == PNK_ASSIGN)
+        handler.checkAndSetIsDirectRHSAnonFunction(rhs);
+
     return handler.newAssignment(kind, lhs, rhs, op);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::isValidSimpleAssignmentTarget(Node node,
                                                     FunctionCallBehavior behavior /* = ForbidAssignmentToFunctionCalls */)
 {
@@ -9074,16 +9085,18 @@ Parser<ParseHandler>::objectLiteral(Yiel
             return null();
 
         if (propType == PropertyType::Normal) {
             Node propExpr = assignExpr(InAllowed, yieldHandling, TripledotProhibited,
                                        possibleError);
             if (!propExpr)
                 return null();
 
+            handler.checkAndSetIsDirectRHSAnonFunction(propExpr);
+
             if (foldConstants && !FoldConstants(context, &propExpr, this))
                 return null();
 
             if (propAtom == context->names().proto) {
                 if (seenPrototypeMutation) {
                     // Directly report the error when we're not in a
                     // destructuring context.
                     if (!possibleError) {
@@ -9186,43 +9199,45 @@ Parser<ParseHandler>::objectLiteral(Yiel
                 // Clearing `inDestructuringDecl` allows name use to be noted
                 // in Parser::identifierReference. See bug 1255167.
                 AutoClearInDestructuringDecl autoClear(pc);
                 rhs = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
                 if (!rhs)
                     return null();
             }
 
+            handler.checkAndSetIsDirectRHSAnonFunction(rhs);
+
             Node propExpr = handler.newAssignment(PNK_ASSIGN, lhs, rhs, JSOP_NOP);
             if (!propExpr)
                 return null();
 
             if (!handler.addPropertyDefinition(literal, propName, propExpr))
                 return null();
 
             if (!abortIfSyntaxParser())
                 return null();
         } else {
-            // FIXME: Implement ES6 function "name" property semantics
-            // (bug 883377).
             RootedAtom funName(context);
             if (!tokenStream.isCurrentTokenType(TOK_RB)) {
                 funName = propAtom;
 
                 if (propType == PropertyType::Getter || propType == PropertyType::Setter) {
                     funName = prefixAccessorName(propType, propAtom);
                     if (!funName)
                         return null();
                 }
             }
 
             Node fn = methodDefinition(propType, funName);
             if (!fn)
                 return null();
 
+            handler.checkAndSetIsDirectRHSAnonFunction(fn);
+
             JSOp op = JSOpFromPropertyType(propType);
             if (!handler.addObjectMethodDefinition(literal, propName, fn, op))
                 return null();
         }
 
         if (!tokenStream.getToken(&tt))
             return null();
         if (tt == TOK_RC)
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -340,16 +340,18 @@ class SyntaxParseHandler
 
     Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeElement; }
 
     MOZ_MUST_USE bool addCatchBlock(Node catchList, Node letBlock, Node catchName,
                                     Node catchGuard, Node catchBody) { return true; }
 
     MOZ_MUST_USE bool setLastFunctionFormalParameterDefault(Node funcpn, Node pn) { return true; }
 
+    void checkAndSetIsDirectRHSAnonFunction(Node pn) {}
+
     Node newFunctionStatement() { return NodeFunctionDefinition; }
     Node newFunctionExpression() { return NodeFunctionDefinition; }
     Node newArrowFunction() { return NodeFunctionDefinition; }
 
     bool setComprehensionLambdaBody(Node pn, Node body) { return true; }
     void setFunctionFormalParametersAndBody(Node pn, Node kid) {}
     void setFunctionBody(Node pn, Node kid) {}
     void setFunctionBox(Node pn, FunctionBox* funbox) {}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2079,33 +2079,33 @@ DefinePropertyById(JSContext* cx, Handle
     // possibly-null JSNatives (or possibly-null JSFunction* if JSPROP_GETTER or
     // JSPROP_SETTER is appropriately set), or both are the well-known property
     // stubs.  The subsequent block must handle only the first of these cases,
     // so carefully exclude the latter case.
     if (!(attrs & JSPROP_PROPOP_ACCESSORS) &&
         getter != JS_PropertyStub && setter != JS_StrictPropertyStub)
     {
         if (getter && !(attrs & JSPROP_GETTER)) {
-            RootedAtom atom(cx, IdToFunctionName(cx, id, "get"));
+            RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get));
             if (!atom)
                 return false;
             JSFunction* getobj = NewNativeFunction(cx, (Native) getter, 0, atom);
             if (!getobj)
                 return false;
 
             if (get.info)
                 getobj->setJitInfo(get.info);
 
             getter = JS_DATA_TO_FUNC_PTR(GetterOp, getobj);
             attrs |= JSPROP_GETTER;
         }
         if (setter && !(attrs & JSPROP_SETTER)) {
             // Root just the getter, since the setter is not yet a JSObject.
             AutoRooterGetterSetter getRoot(cx, JSPROP_GETTER, &getter, nullptr);
-            RootedAtom atom(cx, IdToFunctionName(cx, id, "set"));
+            RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Set));
             if (!atom)
                 return false;
             JSFunction* setobj = NewNativeFunction(cx, (Native) setter, 1, atom);
             if (!setobj)
                 return false;
 
             if (set.info)
                 setobj->setJitInfo(set.info);
@@ -3599,17 +3599,17 @@ JS_PUBLIC_API(JSObject*)
 JS_GetFunctionObject(JSFunction* fun)
 {
     return fun;
 }
 
 JS_PUBLIC_API(JSString*)
 JS_GetFunctionId(JSFunction* fun)
 {
-    return fun->name();
+    return fun->explicitName();
 }
 
 JS_PUBLIC_API(JSString*)
 JS_GetFunctionDisplayId(JSFunction* fun)
 {
     return fun->displayAtom();
 }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -805,17 +805,17 @@ js::ReportIsNullOrUndefined(JSContext* c
 void
 js::ReportMissingArg(JSContext* cx, HandleValue v, unsigned arg)
 {
     char argbuf[11];
     UniqueChars bytes;
 
     SprintfLiteral(argbuf, "%u", arg);
     if (IsFunctionObject(v)) {
-        RootedAtom name(cx, v.toObject().as<JSFunction>().name());
+        RootedAtom name(cx, v.toObject().as<JSFunction>().explicitName());
         bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, name);
         if (!bytes)
             return;
     }
     JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                JSMSG_MISSING_FUN_ARG,
                                argbuf, bytes ? bytes.get() : "");
 }
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -557,17 +557,17 @@ js::XDRInterpretedFunction(XDRState<mode
             JSAutoByteString funNameBytes;
             if (const char* name = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
                 JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                            JSMSG_NOT_SCRIPTED_FUNCTION, name);
             }
             return false;
         }
 
-        if (fun->name() || fun->hasGuessedAtom())
+        if (fun->explicitName() || fun->hasCompileTimeName() || fun->hasGuessedAtom())
             firstword |= HasAtom;
 
         if (fun->isStarGenerator())
             firstword |= IsStarGenerator;
 
         if (fun->isInterpretedLazy()) {
             // Encode a lazy script.
             firstword |= IsLazy;
@@ -985,18 +985,18 @@ js::FunctionToString(JSContext* cx, Hand
         bool ok;
         if (fun->isStarGenerator() && !fun->isAsync())
             ok = out.append("function* ");
         else
             ok = out.append("function ");
         if (!ok)
             return nullptr;
     }
-    if (fun->name()) {
-        if (!out.append(fun->name()))
+    if (fun->explicitName()) {
+        if (!out.append(fun->explicitName()))
             return nullptr;
     }
 
     if (haveSource && !script->scriptSource()->hasSourceData() &&
         !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
     {
         return nullptr;
     }
@@ -1304,24 +1304,24 @@ JSAtom*
 JSFunction::getUnresolvedName(JSContext* cx)
 {
     MOZ_ASSERT(!IsInternalFunctionObject(*this));
     MOZ_ASSERT(!hasResolvedName());
 
     if (isClassConstructor()) {
         // It's impossible to have an empty named class expression. We use
         // empty as a sentinel when creating default class constructors.
-        MOZ_ASSERT(name() != cx->names().empty);
+        MOZ_ASSERT(explicitOrCompileTimeName() != cx->names().empty);
 
         // Unnamed class expressions should not get a .name property at all.
-        return name();
+        return explicitOrCompileTimeName();
     }
 
-    // Returns the empty string for unnamed functions (FIXME: bug 883377).
-    return name() != nullptr ? name() : cx->names().empty;
+    return explicitOrCompileTimeName() != nullptr ? explicitOrCompileTimeName()
+                                                  : cx->names().empty;
 }
 
 static const js::Value&
 BoundFunctionEnvironmentSlotValue(const JSFunction* fun, uint32_t slotIndex)
 {
     MOZ_ASSERT(fun->isBoundFunction());
     MOZ_ASSERT(fun->environment()->is<CallObject>());
     CallObject* callObject = &fun->environment()->as<CallObject>();
@@ -2074,44 +2074,119 @@ js::CloneFunctionAndScript(JSContext* cx
  * Function names are always strings. If id is the well-known @@iterator
  * symbol, this returns "[Symbol.iterator]".  If a prefix is supplied the final
  * name is |prefix + " " + name|. A prefix cannot be supplied if id is a
  * symbol value.
  *
  * Implements steps 3-5 of 9.2.11 SetFunctionName in ES2016.
  */
 JSAtom*
-js::IdToFunctionName(JSContext* cx, HandleId id, const char* prefix /* = nullptr */)
+js::IdToFunctionName(JSContext* cx, HandleId id,
+                     FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */)
 {
-    if (JSID_IS_ATOM(id) && !prefix)
+    // No prefix fastpath.
+    if (JSID_IS_ATOM(id) && prefixKind == FunctionPrefixKind::None)
         return JSID_TO_ATOM(id);
 
-    // Step 3.
-    MOZ_ASSERT_IF(prefix, !JSID_IS_SYMBOL(id));
+    // Step 3 (implicit).
 
     // Step 4.
     if (JSID_IS_SYMBOL(id)) {
+        // Step 4.a.
         RootedAtom desc(cx, JSID_TO_SYMBOL(id)->description());
+
+        // Step 4.b, no prefix fastpath.
+        if (!desc && prefixKind == FunctionPrefixKind::None)
+            return cx->names().empty;
+
+        // Step 5 (reordered).
         StringBuffer sb(cx);
-        if (!sb.append('[') || !sb.append(desc) || !sb.append(']'))
-            return nullptr;
+        if (prefixKind == FunctionPrefixKind::Get) {
+            if (!sb.append("get "))
+                return nullptr;
+        } else if (prefixKind == FunctionPrefixKind::Set) {
+            if (!sb.append("set "))
+                return nullptr;
+        }
+
+        // Step 4.b.
+        if (desc) {
+            // Step 4.c.
+            if (!sb.append('[') || !sb.append(desc) || !sb.append(']'))
+                return nullptr;
+        }
         return sb.finishAtom();
     }
 
+    RootedValue idv(cx, IdToValue(id));
+    RootedAtom name(cx, ToAtom<CanGC>(cx, idv));
+    if (!name)
+        return nullptr;
+
     // Step 5.
-    RootedValue idv(cx, IdToValue(id));
-    if (!prefix)
-        return ToAtom<CanGC>(cx, idv);
+    return NameToFunctionName(cx, name, prefixKind);
+}
+
+JSAtom*
+js::NameToFunctionName(ExclusiveContext* cx, HandleAtom name,
+                       FunctionPrefixKind prefixKind /* = FunctionPrefixKind::None */)
+{
+    if (prefixKind == FunctionPrefixKind::None)
+        return name;
 
     StringBuffer sb(cx);
-    if (!sb.append(prefix, strlen(prefix)) || !sb.append(' ') || !sb.append(ToAtom<CanGC>(cx, idv)))
+    if (prefixKind == FunctionPrefixKind::Get) {
+        if (!sb.append("get "))
+            return nullptr;
+    } else {
+        if (!sb.append("set "))
+            return nullptr;
+    }
+    if (!sb.append(name))
         return nullptr;
     return sb.finishAtom();
 }
 
+bool
+js::SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name,
+                               FunctionPrefixKind prefixKind)
+{
+    MOZ_ASSERT(name.isString() || name.isSymbol() || name.isNumber());
+
+    if (fun->isClassConstructor()) {
+        // A class may have static 'name' method or accessor.
+        RootedId nameId(cx, NameToId(cx->names().name));
+        bool result;
+        if (!HasOwnProperty(cx, fun, nameId, &result))
+            return false;
+
+        if (result)
+            return true;
+    } else {
+        // Anonymous function shouldn't have own 'name' property at this point.
+        MOZ_ASSERT(!fun->containsPure(cx->names().name));
+    }
+
+    RootedId id(cx);
+    if (!ValueToId<CanGC>(cx, name, &id))
+        return false;
+
+    RootedAtom funNameAtom(cx, IdToFunctionName(cx, id, prefixKind));
+    if (!funNameAtom)
+        return false;
+
+    RootedValue funNameVal(cx, StringValue(funNameAtom));
+    if (!NativeDefineProperty(cx, fun, cx->names().name, funNameVal, nullptr, nullptr,
+                              JSPROP_READONLY))
+    {
+        return false;
+    }
+    return true;
+}
+
 JSFunction*
 js::DefineFunction(JSContext* cx, HandleObject obj, HandleId id, Native native,
                    unsigned nargs, unsigned flags, AllocKind allocKind /* = AllocKind::FUNCTION */)
 {
     GetterOp gop;
     SetterOp sop;
     if (flags & JSFUN_STUB_GSOPS) {
         /*
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -26,16 +26,22 @@ struct JSAtomState;
 
 static const uint32_t JSSLOT_BOUND_FUNCTION_TARGET     = 2;
 static const uint32_t JSSLOT_BOUND_FUNCTION_THIS       = 3;
 static const uint32_t JSSLOT_BOUND_FUNCTION_ARGS       = 4;
 
 static const char FunctionConstructorMedialSigils[] = ") {\n";
 static const char FunctionConstructorFinalBrace[] = "\n}";
 
+enum class FunctionPrefixKind {
+    None,
+    Get,
+    Set
+};
+
 class JSFunction : public js::NativeObject
 {
   public:
     static const js::Class class_;
 
     enum FunctionKind {
         NormalFunction = 0,
         Arrow,                      /* ES6 '(args) => body' syntax */
@@ -54,16 +60,19 @@ class JSFunction : public js::NativeObje
         BOUND_FUN        = 0x0008,  /* function was created with Function.prototype.bind. */
         HAS_GUESSED_ATOM = 0x0020,  /* function had no explicit name, but a
                                        name was guessed for it anyway */
         LAMBDA           = 0x0040,  /* function comes from a FunctionExpression, ArrowFunction, or
                                        Function() call (not a FunctionDeclaration or nonstandard
                                        function-statement) */
         SELF_HOSTED      = 0x0080,  /* function is self-hosted builtin and must not be
                                        decompilable nor constructible. */
+        HAS_COMPILE_TIME_NAME = 0x0100, /* function had no explicit name, but a
+                                           name was set by SetFunctionName
+                                           at compile time */
         INTERPRETED_LAZY = 0x0200,  /* function is interpreted but doesn't have a script yet */
         RESOLVED_LENGTH  = 0x0400,  /* f.length has been resolved (see fun_resolve). */
         RESOLVED_NAME    = 0x0800,  /* f.name has been resolved (see fun_resolve). */
 
         FUNCTION_KIND_SHIFT = 13,
         FUNCTION_KIND_MASK  = 0x7 << FUNCTION_KIND_SHIFT,
 
         ASMJS_KIND = AsmJS << FUNCTION_KIND_SHIFT,
@@ -87,17 +96,17 @@ class JSFunction : public js::NativeObje
         INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR,
         INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND,
         INTERPRETED_LAMBDA_GENERATOR = INTERPRETED | LAMBDA,
         INTERPRETED_NORMAL = INTERPRETED | CONSTRUCTOR,
         INTERPRETED_GENERATOR = INTERPRETED,
         NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME,
 
         STABLE_ACROSS_CLONES = CONSTRUCTOR | HAS_GUESSED_ATOM | LAMBDA |
-                               SELF_HOSTED | FUNCTION_KIND_MASK
+                               SELF_HOSTED | HAS_COMPILE_TIME_NAME | FUNCTION_KIND_MASK
     };
 
     static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS,
                   "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong");
     static_assert(((FunctionKindLimit - 1) << FUNCTION_KIND_SHIFT) <= FUNCTION_KIND_MASK,
                   "FunctionKind doesn't fit into flags_");
 
   private:
@@ -171,16 +180,17 @@ class JSFunction : public js::NativeObje
     bool isNative()                 const { return !isInterpreted(); }
 
     bool isConstructor()            const { return flags() & CONSTRUCTOR; }
 
     /* Possible attributes of a native function: */
     bool isAsmJSNative()            const { return kind() == AsmJS; }
 
     /* Possible attributes of an interpreted function: */
+    bool hasCompileTimeName()       const { return flags() & HAS_COMPILE_TIME_NAME; }
     bool hasGuessedAtom()           const { return flags() & HAS_GUESSED_ATOM; }
     bool isLambda()                 const { return flags() & LAMBDA; }
     bool isBoundFunction()          const { return flags() & BOUND_FUN; }
     bool isInterpretedLazy()        const { return flags() & INTERPRETED_LAZY; }
     bool hasScript()                const { return flags() & INTERPRETED; }
 
     bool infallibleIsDefaultClassConstructor(JSContext* cx) const;
 
@@ -212,17 +222,17 @@ class JSFunction : public js::NativeObje
     }
 
     /* Compound attributes: */
     bool isBuiltin() const {
         return (isNative() && !isAsmJSNative()) || isSelfHostedBuiltin();
     }
 
     bool isNamedLambda() const {
-        return isLambda() && displayAtom() && !hasGuessedAtom();
+        return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom();
     }
 
     bool hasLexicalThis() const {
         return isArrow() || nonLazyScript()->isGeneratorExp();
     }
 
     bool isBuiltinFunctionConstructor();
 
@@ -296,36 +306,49 @@ class JSFunction : public js::NativeObje
             nonLazyScript()->setAsyncKind(asyncKind);
     }
 
     static bool getUnresolvedLength(JSContext* cx, js::HandleFunction fun,
                                     js::MutableHandleValue v);
 
     JSAtom* getUnresolvedName(JSContext* cx);
 
-    JSAtom* name() const { return hasGuessedAtom() ? nullptr : atom_.get(); }
-
-    // Because display names (see Debugger.Object.displayName) are already stored
-    // on functions and will always contain a valid es6 function name, as described
-    // in "ECMA-262 (2016-02-27) 9.2.11 SetFunctionName," we have opted to save
-    // memory by parsing the existing display name when a function's name property
-    // is accessed.
-    JSAtom* functionName(JSContext* cx) const;
+    JSAtom* explicitName() const {
+        return (hasCompileTimeName() || hasGuessedAtom()) ? nullptr : atom_.get();
+    }
+    JSAtom* explicitOrCompileTimeName() const {
+        return hasGuessedAtom() ? nullptr : atom_.get();
+    }
 
     void initAtom(JSAtom* atom) { atom_.init(atom); }
 
     void setAtom(JSAtom* atom) { atom_ = atom; }
 
     JSAtom* displayAtom() const {
         return atom_;
     }
 
+    void setCompileTimeName(JSAtom* atom) {
+        MOZ_ASSERT(!atom_);
+        MOZ_ASSERT(atom);
+        MOZ_ASSERT(!hasGuessedAtom());
+        MOZ_ASSERT(!isClassConstructor());
+        atom_ = atom;
+        flags_ |= HAS_COMPILE_TIME_NAME;
+    }
+    JSAtom* compileTimeName() const {
+        MOZ_ASSERT(hasCompileTimeName());
+        MOZ_ASSERT(atom_);
+        return atom_;
+    }
+
     void setGuessedAtom(JSAtom* atom) {
         MOZ_ASSERT(!atom_);
         MOZ_ASSERT(atom);
+        MOZ_ASSERT(!hasCompileTimeName());
         MOZ_ASSERT(!hasGuessedAtom());
         atom_ = atom;
         flags_ |= HAS_GUESSED_ATOM;
     }
     void clearGuessedAtom() {
         MOZ_ASSERT(hasGuessedAtom());
         MOZ_ASSERT(atom_);
         atom_ = nullptr;
@@ -659,17 +682,26 @@ enum NewFunctionProtoHandling {
 extern JSFunction*
 NewFunctionWithProto(ExclusiveContext* cx, JSNative native, unsigned nargs,
                      JSFunction::Flags flags, HandleObject enclosingEnv, HandleAtom atom,
                      HandleObject proto, gc::AllocKind allocKind = gc::AllocKind::FUNCTION,
                      NewObjectKind newKind = GenericObject,
                      NewFunctionProtoHandling protoHandling = NewFunctionClassProto);
 
 extern JSAtom*
-IdToFunctionName(JSContext* cx, HandleId id, const char* prefix = nullptr);
+IdToFunctionName(JSContext* cx, HandleId id,
+                 FunctionPrefixKind prefixKind = FunctionPrefixKind::None);
+
+extern JSAtom*
+NameToFunctionName(ExclusiveContext* cx, HandleAtom name,
+                   FunctionPrefixKind prefixKind = FunctionPrefixKind::None);
+
+extern bool
+SetFunctionNameIfNoOwnName(JSContext* cx, HandleFunction fun, HandleValue name,
+                           FunctionPrefixKind prefixKind);
 
 extern JSFunction*
 DefineFunction(JSContext* cx, HandleObject obj, HandleId id, JSNative native,
                unsigned nargs, unsigned flags,
                gc::AllocKind allocKind = gc::AllocKind::FUNCTION);
 
 bool
 FunctionHasResolveHook(const JSAtomState& atomState, jsid id);
--- a/js/src/jsfuninlines.h
+++ b/js/src/jsfuninlines.h
@@ -11,17 +11,17 @@
 
 #include "vm/EnvironmentObject.h"
 
 namespace js {
 
 inline const char*
 GetFunctionNameBytes(JSContext* cx, JSFunction* fun, JSAutoByteString* bytes)
 {
-    if (JSAtom* name = fun->name())
+    if (JSAtom* name = fun->explicitName())
         return bytes->encodeLatin1(cx, name);
     return js_anonymous_str;
 }
 
 static inline JSObject*
 SkipEnvironmentObjects(JSObject* env)
 {
     if (!env)
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-assignment.js
@@ -0,0 +1,139 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous function name should be set based on assignment";
+
+print(BUGNUMBER + ": " + summary);
+
+var fooSymbol = Symbol("foo");
+var emptySymbol = Symbol("");
+var undefSymbol = Symbol();
+var globalVar;
+
+var exprs = [
+    ["function() {}", false],
+    ["function named() {}", true],
+    ["function*() {}", false],
+    ["function* named() {}", true],
+    ["async function() {}", false],
+    ["async function named() {}", true],
+    ["() => {}", false],
+    ["async () => {}", false],
+    ["class {}", false],
+    ["class named {}", true],
+];
+
+function testAssignmentExpression(expr, named) {
+    eval(`
+    var assignment;
+    assignment = ${expr};
+    assertEq(assignment.name, named ? "named" : "assignment");
+
+    globalVar = ${expr};
+    assertEq(globalVar.name, named ? "named" : "globalVar");
+
+    var obj = { dynamic: null };
+    with (obj) {
+        dynamic = ${expr};
+    }
+    assertEq(obj.dynamic.name, named ? "named" : "dynamic");
+
+    (function namedLambda(param1, param2) {
+        var assignedToNamedLambda;
+        assignedToNamedLambda = namedLambda = ${expr};
+        assertEq(namedLambda.name, "namedLambda");
+        assertEq(assignedToNamedLambda.name, named ? "named" : "namedLambda");
+
+        param1 = ${expr};
+        assertEq(param1.name, named ? "named" : "param1");
+
+        {
+            let param1 = ${expr};
+            assertEq(param1.name, named ? "named" : "param1");
+
+            param2 = ${expr};
+            assertEq(param2.name, named ? "named" : "param2");
+        }
+    })();
+
+    {
+        let nextedLexical1, nextedLexical2;
+        {
+            let nextedLexical1 = ${expr};
+            assertEq(nextedLexical1.name, named ? "named" : "nextedLexical1");
+
+            nextedLexical2 = ${expr};
+            assertEq(nextedLexical2.name, named ? "named" : "nextedLexical2");
+        }
+    }
+    `);
+
+    // Not applicable cases: not IsIdentifierRef.
+    eval(`
+    var inParen;
+    (inParen) = ${expr};
+    assertEq(inParen.name, named ? "named" : "");
+    `);
+
+    // Not applicable cases: not direct RHS.
+    if (!expr.includes("=>")) {
+        eval(`
+        var a = true && ${expr};
+        assertEq(a.name, named ? "named" : "");
+        `);
+    } else {
+        // Arrow function cannot be RHS of &&.
+        eval(`
+        var a = true && (${expr});
+        assertEq(a.name, named ? "named" : "");
+        `);
+    }
+
+    // Not applicable cases: property.
+    eval(`
+    var obj = {};
+
+    obj.prop = ${expr};
+    assertEq(obj.prop.name, named ? "named" : "");
+
+    obj["literal"] = ${expr};
+    assertEq(obj["literal"].name, named ? "named" : "");
+    `);
+
+    // Not applicable cases: assigned again.
+    eval(`
+    var tmp = [${expr}];
+    assertEq(tmp[0].name, named ? "named" : "");
+
+    var assignment;
+    assignment = tmp[0];
+    assertEq(assignment.name, named ? "named" : "");
+    `);
+}
+for (var [expr, named] of exprs) {
+    testAssignmentExpression(expr, named);
+}
+
+function testVariableDeclaration(expr, named) {
+    eval(`
+    var varDecl = ${expr};
+    assertEq(varDecl.name, named ? "named" : "varDecl");
+    `);
+}
+for (var [expr, named] of exprs) {
+    testVariableDeclaration(expr, named);
+}
+
+function testLexicalBinding(expr, named) {
+    eval(`
+    let lexical = ${expr};
+    assertEq(lexical.name, named ? "named" : "lexical");
+
+    const constLexical = ${expr};
+    assertEq(constLexical.name, named ? "named" : "constLexical");
+    `);
+}
+for (var [expr, named] of exprs) {
+    testLexicalBinding(expr, named);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-binding.js
@@ -0,0 +1,54 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous function name should be set based on binding pattern";
+
+print(BUGNUMBER + ": " + summary);
+
+var exprs = [
+    ["function() {}", false],
+    ["function named() {}", true],
+    ["function*() {}", false],
+    ["function* named() {}", true],
+    ["async function() {}", false],
+    ["async function named() {}", true],
+    ["() => {}", false],
+    ["async () => {}", false],
+    ["class {}", false],
+    ["class named {}", true],
+];
+
+function testAssignmentProperty(expr, named) {
+    var f = eval(`(function({ prop1 = ${expr} }) { return prop1; })`);
+    assertEq(f({}).name, named ? "named" : "prop1");
+
+    eval(`
+    var { prop1 = ${expr} } = {};
+    assertEq(prop1.name, named ? "named" : "prop1");
+    `);
+}
+for (var [expr, named] of exprs) {
+    testAssignmentProperty(expr, named);
+}
+
+function testAssignmentElement(expr, named) {
+    var f = eval(`(function([elem1 = ${expr}]) { return elem1; })`);
+    assertEq(f([]).name, named ? "named" : "elem1");
+
+    eval(`
+    var [elem1 = ${expr}] = [];
+    assertEq(elem1.name, named ? "named" : "elem1");
+    `);
+}
+for (var [expr, named] of exprs) {
+    testAssignmentElement(expr, named);
+}
+
+function testSingleNameBinding(expr, named) {
+    var f = eval(`(function(param1 = ${expr}) { return param1; })`);
+    assertEq(f().name, named ? "named" : "param1");
+}
+for (var [expr, named] of exprs) {
+    testSingleNameBinding(expr, named);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-class.js
@@ -0,0 +1,32 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous class with name method shouldn't be affected by assignment";
+
+print(BUGNUMBER + ": " + summary);
+
+var classWithStaticNameMethod = class { static name() {} };
+assertEq(typeof classWithStaticNameMethod.name, "function");
+
+var classWithStaticNameGetter = class { static get name() { return "static name"; } };
+assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameGetter, "name").get, "function");
+assertEq(classWithStaticNameGetter.name, "static name");
+
+var classWithStaticNameSetter = class { static set name(v) {} };
+assertEq(typeof Object.getOwnPropertyDescriptor(classWithStaticNameSetter, "name").set, "function");
+
+var n = "NAME".toLowerCase();
+var classWithStaticNameMethodComputed = class { static [n]() {} };
+assertEq(typeof classWithStaticNameMethodComputed.name, "function");
+
+// It doesn't apply for non-static method.
+
+var classWithNameMethod = class { name() {} };
+assertEq(classWithNameMethod.name, "classWithNameMethod");
+
+var classWithNameGetter = class { get name() { return "name"; } };
+assertEq(classWithNameGetter.name, "classWithNameGetter");
+
+var classWithNameSetter = class { set name(v) {} };
+assertEq(classWithNameSetter.name, "classWithNameSetter");
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-for.js
@@ -0,0 +1,31 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous function name should be set based on for-in initializer";
+
+print(BUGNUMBER + ": " + summary);
+
+var exprs = [
+    ["function() {}", false],
+    ["function named() {}", true],
+    ["function*() {}", false],
+    ["function* named() {}", true],
+    ["async function() {}", false],
+    ["async function named() {}", true],
+    ["() => {}", false],
+    ["async () => {}", false],
+    ["class {}", false],
+    ["class named {}", true],
+];
+
+function testForInHead(expr, named) {
+    eval(`
+    for (var forInHead = ${expr} in {}) {
+    }
+    `);
+    assertEq(forInHead.name, named ? "named" : "forInHead");
+}
+for (var [expr, named] of exprs) {
+    testForInHead(expr, named);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-method.js
@@ -0,0 +1,70 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous function name should be set based on method definition";
+
+print(BUGNUMBER + ": " + summary);
+
+var fooSymbol = Symbol("foo");
+var emptySymbol = Symbol("");
+var undefSymbol = Symbol();
+
+function testMethod(prefix, classPrefix="", prototype=false) {
+    var param = (prefix == "set" || prefix == "static set") ? "v" : "";
+    var sep = classPrefix ? "" : ",";
+    var objOrClass = eval(`(${classPrefix}{
+        ${prefix} prop(${param}) {} ${sep}
+        ${prefix} "literal"(${param}) {} ${sep}
+        ${prefix} ""(${param}) {} ${sep}
+        ${prefix} 5(${param}) {} ${sep}
+        ${prefix} [Symbol.iterator](${param}) {} ${sep}
+        ${prefix} [fooSymbol](${param}) {} ${sep}
+        ${prefix} [emptySymbol](${param}) {} ${sep}
+        ${prefix} [undefSymbol](${param}) {} ${sep}
+        ${prefix} [/a/](${param}) {} ${sep}
+    })`);
+
+    var target = prototype ? objOrClass.prototype : objOrClass;
+
+    function testOne(methodName, expectedName) {
+        var f;
+        if (prefix == "get" || prefix == "static get") {
+            f = Object.getOwnPropertyDescriptor(target, methodName).get;
+            expectedName = "get " + expectedName;
+        } else if (prefix == "set" || prefix == "static set") {
+            f = Object.getOwnPropertyDescriptor(target, methodName).set;
+            expectedName = "set " + expectedName;
+        } else {
+            f = Object.getOwnPropertyDescriptor(target, methodName).value;
+        }
+
+        assertEq(f.name, expectedName);
+    }
+    testOne("prop", "prop");
+    testOne("literal", "literal");
+    testOne("", "");
+    testOne(5, "5");
+    testOne(Symbol.iterator, "[Symbol.iterator]");
+    testOne(fooSymbol, "[foo]");
+    testOne(emptySymbol, "[]");
+    testOne(undefSymbol, "");
+    testOne(/a/, "/a/");
+}
+testMethod("");
+testMethod("*");
+testMethod("async");
+testMethod("get");
+testMethod("set");
+
+testMethod("", "class", true);
+testMethod("*", "class", true);
+testMethod("async", "class", true);
+testMethod("get", "class", true);
+testMethod("set", "class", true);
+
+testMethod("static", "class");
+testMethod("static *", "class");
+testMethod("static async", "class");
+testMethod("static get", "class");
+testMethod("static set", "class");
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Function/function-name-property.js
@@ -0,0 +1,58 @@
+var BUGNUMBER = 883377;
+var summary = "Anonymous function name should be set based on property name";
+
+print(BUGNUMBER + ": " + summary);
+
+var fooSymbol = Symbol("foo");
+var emptySymbol = Symbol("");
+var undefSymbol = Symbol();
+
+var exprs = [
+    ["function() {}", false],
+    ["function named() {}", true],
+    ["function*() {}", false],
+    ["function* named() {}", true],
+    ["async function() {}", false],
+    ["async function named() {}", true],
+    ["() => {}", false],
+    ["async () => {}", false],
+    ["class {}", false],
+    ["class named {}", true],
+];
+
+function testPropertyDefinition(expr, named) {
+    var obj = eval(`({
+        prop: ${expr},
+        "literal": ${expr},
+        "": ${expr},
+        5: ${expr},
+        0.4: ${expr},
+        [Symbol.iterator]: ${expr},
+        [fooSymbol]: ${expr},
+        [emptySymbol]: ${expr},
+        [undefSymbol]: ${expr},
+        [/a/]: ${expr},
+    })`);
+    assertEq(obj.prop.name, named ? "named" : "prop");
+    assertEq(obj["literal"].name, named ? "named" : "literal");
+    assertEq(obj[""].name, named ? "named" : "");
+    assertEq(obj[5].name, named ? "named" : "5");
+    assertEq(obj[0.4].name, named ? "named" : "0.4");
+    assertEq(obj[Symbol.iterator].name, named ? "named" : "[Symbol.iterator]");
+    assertEq(obj[fooSymbol].name, named ? "named" : "[foo]");
+    assertEq(obj[emptySymbol].name, named ? "named" : "[]");
+    assertEq(obj[undefSymbol].name, named ? "named" : "");
+    assertEq(obj[/a/].name, named ? "named" : "/a/");
+
+    // Not applicable cases: __proto__.
+    obj = {
+        __proto__: function() {}
+    };
+    assertEq(obj.__proto__.name, "");
+}
+for (var [expr, named] of exprs) {
+    testPropertyDefinition(expr, named);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/vm/AsyncFunction.cpp
+++ b/js/src/vm/AsyncFunction.cpp
@@ -114,30 +114,33 @@ js::WrapAsyncFunctionWithProto(JSContext
 {
     MOZ_ASSERT(unwrapped->isStarGenerator());
     MOZ_ASSERT(proto, "We need an explicit prototype to avoid the default"
                       "%FunctionPrototype% fallback in NewFunctionWithProto().");
 
     // Create a new function with AsyncFunctionPrototype, reusing the name and
     // the length of `unwrapped`.
 
-    RootedAtom funName(cx, unwrapped->name());
+    RootedAtom funName(cx, unwrapped->explicitName());
     uint16_t length;
     if (!JSFunction::getLength(cx, unwrapped, &length))
         return nullptr;
 
     // Steps 3 (partially).
     RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncFunction, length,
                                                     JSFunction::NATIVE_FUN, nullptr,
                                                     funName, proto,
                                                     AllocKind::FUNCTION_EXTENDED,
                                                     TenuredObject));
     if (!wrapped)
         return nullptr;
 
+    if (unwrapped->hasCompileTimeName())
+        wrapped->setCompileTimeName(unwrapped->compileTimeName());
+
     // Link them to each other to make GetWrappedAsyncFunction and
     // GetUnwrappedAsyncFunction work.
     unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
     wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
 
     return wrapped;
 }
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -9664,17 +9664,17 @@ DebuggerObject::getGlobal(JSContext* cx,
     return dbg->wrapDebuggeeObject(cx, global, result);
 }
 
 JSAtom*
 DebuggerObject::name() const
 {
     MOZ_ASSERT(isFunction());
 
-    return referent()->as<JSFunction>().name();
+    return referent()->as<JSFunction>().explicitName();
 }
 
 JSAtom*
 DebuggerObject::displayName() const
 {
     MOZ_ASSERT(isFunction());
 
     return referent()->as<JSFunction>().displayAtom();
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -798,20 +798,20 @@ GlobalObject::getSelfHostedFunction(JSCo
                                     HandlePropertyName selfHostedName, HandleAtom name,
                                     unsigned nargs, MutableHandleValue funVal)
 {
     bool exists = false;
     if (!GlobalObject::maybeGetIntrinsicValue(cx, global, selfHostedName, funVal, &exists))
         return false;
     if (exists) {
         RootedFunction fun(cx, &funVal.toObject().as<JSFunction>());
-        if (fun->name() == name)
+        if (fun->explicitName() == name)
             return true;
 
-        if (fun->name() == selfHostedName) {
+        if (fun->explicitName() == selfHostedName) {
             // This function was initially cloned because it was called by
             // other self-hosted code, so the clone kept its self-hosted name,
             // instead of getting the name it's intended to have in content
             // compartments. This can happen when a lazy builtin is initialized
             // after self-hosted code for another builtin used the same
             // function. In that case, we need to change the function's name,
             // which is ok because it can't have been exposed to content
             // before.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1863,17 +1863,16 @@ CASE(EnableInterruptsPseudoOpcode)
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED182)
 CASE(JSOP_UNUSED183)
 CASE(JSOP_UNUSED187)
 CASE(JSOP_UNUSED192)
 CASE(JSOP_UNUSED209)
 CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED211)
 CASE(JSOP_UNUSED219)
 CASE(JSOP_UNUSED220)
@@ -3485,16 +3484,29 @@ CASE(JSOP_TOASYNC)
     JSObject* wrapped = WrapAsyncFunction(cx, unwrapped);
     if (!wrapped)
         goto error;
 
     REGS.sp[-1].setObject(*wrapped);
 }
 END_CASE(JSOP_TOASYNC)
 
+CASE(JSOP_SETFUNNAME)
+{
+    MOZ_ASSERT(REGS.stackDepth() >= 2);
+    FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc));
+    ReservedRooted<Value> name(&rootValue0, REGS.sp[-1]);
+    ReservedRooted<JSFunction*> fun(&rootFunction0, &REGS.sp[-2].toObject().as<JSFunction>());
+    if (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind))
+        goto error;
+
+    REGS.sp--;
+}
+END_CASE(JSOP_SETFUNNAME)
+
 CASE(JSOP_CALLEE)
     MOZ_ASSERT(REGS.fp()->isFunctionFrame());
     PUSH_COPY(REGS.fp()->calleev());
 END_CASE(JSOP_CALLEE)
 
 CASE(JSOP_INITPROP_GETTER)
 CASE(JSOP_INITHIDDENPROP_GETTER)
 CASE(JSOP_INITPROP_SETTER)
@@ -4348,17 +4360,17 @@ js::DefFunOperation(JSContext* cx, Handl
      * current scope chain even for the case of function expression statements
      * and functions defined by eval inside let or with blocks.
      */
     RootedObject parent(cx, envChain);
     while (!parent->isQualifiedVarObj())
         parent = parent->enclosingEnvironment();
 
     /* ES5 10.5 (NB: with subsequent errata). */
-    RootedPropertyName name(cx, fun->name()->asPropertyName());
+    RootedPropertyName name(cx, fun->explicitName()->asPropertyName());
 
     RootedShape shape(cx);
     RootedObject pobj(cx);
     if (!LookupProperty(cx, parent, name, &pobj, &shape))
         return false;
 
     RootedValue rval(cx, ObjectValue(*fun));
 
@@ -4996,17 +5008,17 @@ js::ReportRuntimeLexicalError(JSContext*
                op == JSOP_THROWSETCONST ||
                op == JSOP_THROWSETALIASEDCONST ||
                op == JSOP_THROWSETCALLEE ||
                op == JSOP_GETIMPORT);
 
     RootedPropertyName name(cx);
 
     if (op == JSOP_THROWSETCALLEE) {
-        name = script->functionNonDelazifying()->name()->asPropertyName();
+        name = script->functionNonDelazifying()->explicitName()->asPropertyName();
     } else if (IsLocalOp(op)) {
         name = FrameSlotName(script, pc)->asPropertyName();
     } else if (IsAtomOp(op)) {
         name = script->getName(pc);
     } else {
         MOZ_ASSERT(IsAliasedVarOp(op));
         name = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc);
     }
@@ -5064,18 +5076,18 @@ js::ThrowUninitializedThis(JSContext* cx
             }
         }
         MOZ_ASSERT(fun);
     }
 
     if (fun->isDerivedClassConstructor()) {
         const char* name = "anonymous";
         JSAutoByteString str;
-        if (fun->name()) {
-            if (!AtomToPrintableString(cx, fun->name(), &str))
+        if (fun->explicitName()) {
+            if (!AtomToPrintableString(cx, fun->explicitName(), &str))
                 return false;
             name = str.ptr();
         }
 
         JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS, name);
         return false;
     }
 
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1865,17 +1865,26 @@ 1234567890123456789012345678901234567890
     /*
      * Pops a var environment from the env chain.
      *   Category: Variables and Scopes
      *   Type: Var Scope
      *   Operands:
      *   Stack: =>
      */ \
     macro(JSOP_POPVARENV,          181, "popvarenv",            NULL,  1,  0,  0,  JOF_BYTE) \
-    macro(JSOP_UNUSED182,     182,"unused182",  NULL,     1,  0,  0,  JOF_BYTE) \
+    /*
+     * Pops the top two values on the stack as 'name' and 'fun', defines the
+     * name of 'fun' to 'name' with prefix if any, and pushes 'fun' back onto
+     * the stack.
+     *   Category: Statements
+     *   Type: Function
+     *   Operands: uint8_t prefixKind
+     *   Stack: fun, name => fun
+     */ \
+    macro(JSOP_SETFUNNAME,    182,"setfunname", NULL,     2,  2,  1,  JOF_UINT8) \
     macro(JSOP_UNUSED183,     183,"unused183",  NULL,     1,  0,  0,  JOF_BYTE) \
     \
     /*
      * Pops the top of stack value, pushes property of it onto the stack.
      *
      * Like JSOP_GETPROP but for call context.
      *   Category: Literals
      *   Type: Object
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2874,33 +2874,33 @@ CloneObject(JSContext* cx, HandleNativeO
         if (detect->foundCycle())
             MOZ_CRASH("SelfHosted cloning cannot handle cyclic object graphs.");
     }
 #endif
 
     RootedObject clone(cx);
     if (selfHostedObject->is<JSFunction>()) {
         RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>());
-        bool hasName = selfHostedFunction->name() != nullptr;
+        bool hasName = selfHostedFunction->explicitName() != nullptr;
 
         // Arrow functions use the first extended slot for their lexical |this| value.
         MOZ_ASSERT(!selfHostedFunction->isArrow());
         js::gc::AllocKind kind = hasName
                                  ? gc::AllocKind::FUNCTION_EXTENDED
                                  : selfHostedFunction->getAllocKind();
         MOZ_ASSERT(!CanReuseScriptForClone(cx->compartment(), selfHostedFunction, cx->global()));
         Rooted<LexicalEnvironmentObject*> globalLexical(cx, &cx->global()->lexicalEnvironment());
         RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
         clone = CloneFunctionAndScript(cx, selfHostedFunction, globalLexical, emptyGlobalScope,
                                        kind);
         // To be able to re-lazify the cloned function, its name in the
         // self-hosting compartment has to be stored on the clone.
         if (clone && hasName) {
             clone->as<JSFunction>().setExtendedSlot(LAZY_FUNCTION_NAME_SLOT,
-                                                    StringValue(selfHostedFunction->name()));
+                                                    StringValue(selfHostedFunction->explicitName()));
         }
     } else if (selfHostedObject->is<RegExpObject>()) {
         RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
         RootedAtom source(cx, reobj.getSource());
         MOZ_ASSERT(source->isPermanentAtom());
         clone = RegExpObject::create(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
     } else if (selfHostedObject->is<DateObject>()) {
         clone = JS::NewDateObject(cx, selfHostedObject->as<DateObject>().clippedTime());
@@ -2973,20 +2973,20 @@ JSRuntime::createLazySelfHostedFunctionC
     MOZ_ASSERT(newKind != GenericObject);
 
     RootedAtom funName(cx, name);
     JSFunction* selfHostedFun = getUnclonedSelfHostedFunction(cx, selfHostedName);
     if (!selfHostedFun)
         return false;
 
     if (!selfHostedFun->isClassConstructor() && !selfHostedFun->hasGuessedAtom() &&
-        selfHostedFun->name() != selfHostedName)
+        selfHostedFun->explicitName() != selfHostedName)
     {
         MOZ_ASSERT(selfHostedFun->getExtendedSlot(HAS_SELFHOSTED_CANONICAL_NAME_SLOT).toBoolean());
-        funName = selfHostedFun->name();
+        funName = selfHostedFun->explicitName();
     }
 
     fun.set(NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY,
                                 funName, proto, gc::AllocKind::FUNCTION_EXTENDED, newKind));
     if (!fun)
         return false;
     fun->setIsSelfHostedBuiltin();
     fun->setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, StringValue(selfHostedName));
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -4543,17 +4543,17 @@ TypeScript::printTypes(JSContext* cx, Ha
     else if (script->isForEval())
         fprintf(stderr, "Eval");
     else
         fprintf(stderr, "Main");
     fprintf(stderr, " %#" PRIxPTR " %s:%" PRIuSIZE " ",
             uintptr_t(script.get()), script->filename(), script->lineno());
 
     if (script->functionNonDelazifying()) {
-        if (JSAtom* name = script->functionNonDelazifying()->name())
+        if (JSAtom* name = script->functionNonDelazifying()->explicitName())
             name->dumpCharsNoNewline();
     }
 
     fprintf(stderr, "\n    this:");
     TypeScript::ThisTypes(script)->print();
 
     for (unsigned i = 0;
          script->functionNonDelazifying() && i < script->functionNonDelazifying()->nargs();
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -2792,17 +2792,17 @@ DataViewObject::getter(JSContext* cx, un
     return CallNonGenericMethod<is, getterImpl<ValueGetter> >(cx, args);
 }
 
 template<Value ValueGetter(DataViewObject* view)>
 bool
 DataViewObject::defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto)
 {
     RootedId id(cx, NameToId(name));
-    RootedAtom atom(cx, IdToFunctionName(cx, id, "get"));
+    RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get));
     if (!atom)
         return false;
     unsigned attrs = JSPROP_SHARED | JSPROP_GETTER;
 
     Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal());
     JSObject* getter =
         NewNativeFunction(cx, DataViewObject::getter<ValueGetter>, 0, atom);
     if (!getter)
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -675,17 +675,17 @@ FunctionObject(ParseNode* fn)
     MOZ_ASSERT(fn->isKind(PNK_FUNCTION));
     MOZ_ASSERT(fn->isArity(PN_CODE));
     return fn->pn_funbox->function();
 }
 
 static inline PropertyName*
 FunctionName(ParseNode* fn)
 {
-    if (JSAtom* name = FunctionObject(fn)->name())
+    if (JSAtom* name = FunctionObject(fn)->explicitName())
         return name->asPropertyName();
     return nullptr;
 }
 
 static inline ParseNode*
 FunctionStatementList(ParseNode* fn)
 {
     MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
@@ -8033,17 +8033,17 @@ TryInstantiate(JSContext* cx, CallArgs a
     MOZ_RELEASE_ASSERT(exportObjVal.isObject());
     exportObj.set(&exportObjVal.toObject());
     return true;
 }
 
 static bool
 HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata)
 {
-    RootedAtom name(cx, args.callee().as<JSFunction>().name());
+    RootedAtom name(cx, args.callee().as<JSFunction>().explicitName());
 
     if (cx->isExceptionPending())
         return false;
 
     ScriptSource* source = metadata.scriptSource.get();
 
     // Source discarding is allowed to affect JS semantics because it is never
     // enabled for normal JS content.
@@ -8124,17 +8124,17 @@ InstantiateAsmJS(JSContext* cx, unsigned
 
     args.rval().set(ObjectValue(*exportObj));
     return true;
 }
 
 static JSFunction*
 NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj)
 {
-    RootedAtom name(cx, origFun->name());
+    RootedAtom name(cx, origFun->explicitName());
 
     JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR
                                                   : JSFunction::ASMJS_CTOR;
     JSFunction* moduleFun =
         NewNativeConstructor(cx, InstantiateAsmJS, origFun->nargs(), name,
                              gc::AllocKind::FUNCTION_EXTENDED, TenuredObject,
                              flags);
     if (!moduleFun)
@@ -8844,17 +8844,17 @@ js::AsmJSModuleToString(JSContext* cx, H
     StringBuffer out(cx);
 
     if (addParenToLambda && fun->isLambda() && !out.append("("))
         return nullptr;
 
     if (!out.append("function "))
         return nullptr;
 
-    if (fun->name() && !out.append(fun->name()))
+    if (fun->explicitName() && !out.append(fun->explicitName()))
         return nullptr;
 
     bool haveSource = source->hasSourceData();
     if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
         return nullptr;
 
     if (!haveSource) {
         if (!out.append("() {\n    [sourceless code]\n}"))
@@ -8892,18 +8892,18 @@ js::AsmJSFunctionToString(JSContext* cx,
         return nullptr;
 
     bool haveSource = source->hasSourceData();
     if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
         return nullptr;
 
     if (!haveSource) {
         // asm.js functions can't be anonymous
-        MOZ_ASSERT(fun->name());
-        if (!out.append(fun->name()))
+        MOZ_ASSERT(fun->explicitName());
+        if (!out.append(fun->explicitName()))
             return nullptr;
         if (!out.append("() {\n    [sourceless code]\n}"))
             return nullptr;
     } else {
         Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
         if (!src)
             return nullptr;
         if (!out.append(src))