Bug 1344477 - Part 1: Add JSOP_CALL_IGNORES_RV for function call that ignores return value. r=jandem
authorTooru Fujisawa <arai_a@mac.com>
Sun, 05 Mar 2017 03:17:22 +0900
changeset 348366 27e7cecf46420ac66d14134954c6d10e4a157911
parent 348365 b316fdd7d8fcdf83210bebe6b81f24cce64fc648
child 348367 ebd6a21698472d184f4ff16fc5bcb9e83853ab68
push id39126
push userarchaeopteryx@coole-files.de
push dateSun, 19 Mar 2017 15:37:50 +0000
treeherderautoland@53392221fe78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1344477
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1344477 - Part 1: Add JSOP_CALL_IGNORES_RV for function call that ignores return value. r=jandem
js/public/CallArgs.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/jsfriendapi.h
js/src/jsfun.h
js/src/jsopcode.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/Stack.h
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -123,17 +123,20 @@ class MOZ_STACK_CLASS CallArgsBase : pub
 {
     static_assert(mozilla::IsSame<WantUsedRval, IncludeUsedRval>::value ||
                   mozilla::IsSame<WantUsedRval, NoUsedRval>::value,
                   "WantUsedRval can only be IncludeUsedRval or NoUsedRval");
 
   protected:
     Value* argv_;
     unsigned argc_;
-    bool constructing_;
+    bool constructing_:1;
+
+    // True if the caller does not use the return value.
+    bool ignoresReturnValue_:1;
 
   public:
     // CALLEE ACCESS
 
     /*
      * Returns the function being called, as a value.  Must not be called after
      * rval() has been used!
      */
@@ -159,16 +162,20 @@ class MOZ_STACK_CLASS CallArgsBase : pub
 #ifdef JS_DEBUG
         if (!this->usedRval_)
             CheckIsValidConstructible(calleev());
 #endif
 
         return true;
     }
 
+    bool ignoresReturnValue() const {
+        return ignoresReturnValue_;
+    }
+
     MutableHandleValue newTarget() const {
         MOZ_ASSERT(constructing_);
         return MutableHandleValue::fromMarkedLocation(&this->argv_[argc_]);
     }
 
     /*
      * Returns the |this| value passed to the function.  This method must not
      * be called when the function is being called as a constructor via |new|.
@@ -275,24 +282,27 @@ class MOZ_STACK_CLASS CallArgsBase : pub
 };
 
 } // namespace detail
 
 class MOZ_STACK_CLASS CallArgs : public detail::CallArgsBase<detail::IncludeUsedRval>
 {
   private:
     friend CallArgs CallArgsFromVp(unsigned argc, Value* vp);
-    friend CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, bool constructing);
+    friend CallArgs CallArgsFromSp(unsigned stackSlots, Value* sp, bool constructing,
+                                   bool ignoresReturnValue);
 
-    static CallArgs create(unsigned argc, Value* argv, bool constructing) {
+    static CallArgs create(unsigned argc, Value* argv, bool constructing,
+                           bool ignoresReturnValue = false) {
         CallArgs args;
         args.clearUsedRval();
         args.argv_ = argv;
         args.argc_ = argc;
         args.constructing_ = constructing;
+        args.ignoresReturnValue_ = ignoresReturnValue;
 #ifdef DEBUG
         MOZ_ASSERT(ValueIsNotGray(args.thisv()));
         MOZ_ASSERT(ValueIsNotGray(args.calleev()));
         for (unsigned i = 0; i < argc; ++i)
             MOZ_ASSERT(ValueIsNotGray(argv[i]));
 #endif
         return args;
     }
@@ -311,19 +321,21 @@ CallArgsFromVp(unsigned argc, Value* vp)
 {
     return CallArgs::create(argc, vp + 2, vp[1].isMagic(JS_IS_CONSTRUCTING));
 }
 
 // This method is only intended for internal use in SpiderMonkey.  We may
 // eventually move it to an internal header.  Embedders should use
 // JS::CallArgsFromVp!
 MOZ_ALWAYS_INLINE CallArgs
-CallArgsFromSp(unsigned stackSlots, Value* sp, bool constructing = false)
+CallArgsFromSp(unsigned stackSlots, Value* sp, bool constructing = false,
+               bool ignoresReturnValue = false)
 {
-    return CallArgs::create(stackSlots - constructing, sp - stackSlots, constructing);
+    return CallArgs::create(stackSlots - constructing, sp - stackSlots, constructing,
+                            ignoresReturnValue);
 }
 
 } // namespace JS
 
 /*
  * Macros to hide interpreter stack layout details from a JSNative using its
  * JS::Value* vp parameter.  DO NOT USE THESE!  Instead use JS::CallArgs and
  * friends, above.  These macros will be removed when we change JSNative to
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4585,17 +4585,17 @@ BytecodeEmitter::emitSwitch(ParseNode* p
 
         // Emit code for evaluating cases and jumping to case statements.
         for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
             ParseNode* caseValue = caseNode->caseExpression();
 
             // If the expression is a literal, suppress line number emission so
             // that debugging works more naturally.
             if (caseValue) {
-                if (!emitTree(caseValue,
+                if (!emitTree(caseValue, ValueUsage::WantValue,
                               caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
                 {
                     return false;
                 }
             }
 
             if (!beforeCases) {
                 // prevCase is the previous JSOP_CASE's bytecode offset.
@@ -6613,17 +6613,17 @@ BytecodeEmitter::emitLexicalScopeBody(Pa
         // This block contains function statements whose definitions are
         // hoisted to the top of the block. Emit these as a separate pass
         // before the rest of the block.
         if (!emitHoistedFunctionsInList(body))
             return false;
     }
 
     // Line notes were updated by emitLexicalScope.
-    return emitTree(body, emitLineNote);
+    return emitTree(body, ValueUsage::WantValue, emitLineNote);
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitLexicalScope(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
@@ -6715,19 +6715,19 @@ BytecodeEmitter::emitRequireObjectCoerci
                     JSOP_GETINTRINSIC))    // VAL VAL REQUIREOBJECTCOERCIBLE
     {
         return false;
     }
     if (!emit1(JSOP_UNDEFINED))            // VAL VAL REQUIREOBJECTCOERCIBLE UNDEFINED
         return false;
     if (!emit2(JSOP_PICK, 2))              // VAL REQUIREOBJECTCOERCIBLE UNDEFINED VAL
         return false;
-    if (!emitCall(JSOP_CALL, 1))           // VAL IGNORED
-        return false;
-    checkTypeSet(JSOP_CALL);
+    if (!emitCall(JSOP_CALL_IGNORES_RV, 1))// VAL IGNORED
+        return false;
+    checkTypeSet(JSOP_CALL_IGNORES_RV);
 
     if (!emit1(JSOP_POP))                  // VAL
         return false;
 
     MOZ_ASSERT(depth == this->stackDepth);
     return true;
 }
 
@@ -7237,22 +7237,24 @@ BytecodeEmitter::emitCStyleFor(ParseNode
     // ES6 spec also skips cloning the environment in this case.)
     bool forLoopRequiresFreshening = false;
     if (ParseNode* init = forHead->pn_kid1) {
         // Emit the `init` clause, whether it's an expression or a variable
         // declaration. (The loop variables were hoisted into an enclosing
         // scope, but we still need to emit code for the initializers.)
         if (!updateSourceCoordNotes(init->pn_pos.begin))
             return false;
-        if (!emitTree(init))
-            return false;
-
-        if (!init->isForLoopDeclaration()) {
+        if (init->isForLoopDeclaration()) {
+            if (!emitTree(init))
+                return false;
+        } else {
             // 'init' is an expression, not a declaration. emitTree left its
             // value on the stack.
+            if (!emitTree(init, ValueUsage::IgnoreValue))
+                return false;
             if (!emit1(JSOP_POP))
                 return false;
         }
 
         // ES 13.7.4.8 step 2. The initial freshening.
         //
         // If an initializer let-declaration may be captured during loop iteration,
         // the current scope has an environment.  If so, freshen the current
@@ -7324,17 +7326,17 @@ BytecodeEmitter::emitCStyleFor(ParseNode
 
     // Check for update code to do before the condition (if any).
     // The update code may not be executed at all; it needs its own TDZ cache.
     if (ParseNode* update = forHead->pn_kid3) {
         TDZCheckCache tdzCache(this);
 
         if (!updateSourceCoordNotes(update->pn_pos.begin))
             return false;
-        if (!emitTree(update))
+        if (!emitTree(update, ValueUsage::IgnoreValue))
             return false;
         if (!emit1(JSOP_POP))
             return false;
 
         /* Restore the absolute line number for source note readers. */
         uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end);
         if (currentLine() != lineNum) {
             if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum)))
@@ -8681,18 +8683,19 @@ BytecodeEmitter::emitStatement(ParseNode
             innermostNestableControl->as<LabelControl>().startOffset() >= offset())
         {
             useful = true;
         }
     }
 
     if (useful) {
         JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP;
+        ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue;
         MOZ_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP));
-        if (!emitTree(pn2))
+        if (!emitTree(pn2, valueUsage))
             return false;
         if (!emit1(op))
             return false;
     } else if (pn->isDirectivePrologueMember()) {
         // Don't complain about directive prologue members; just don't emit
         // their code.
     } else {
         if (JSAtom* atom = pn->isStringExprStatement()) {
@@ -9060,17 +9063,17 @@ BytecodeEmitter::emitOptimizeSpread(Pars
     if (!emit1(JSOP_POP))
         return false;
 
     *emitted = true;
     return true;
 }
 
 bool
-BytecodeEmitter::emitCallOrNew(ParseNode* pn)
+BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
     bool callop = pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE);
     /*
      * Emit callable invocation or operator new (constructor call) code.
      * First, emit code for the left operand to evaluate the callable or
      * constructable object expression.
      *
      * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc.
@@ -9241,23 +9244,30 @@ BytecodeEmitter::emitCallOrNew(ParseNode
             } else {
                 if (!emitDupAt(2))
                     return false;
             }
         }
     }
 
     if (!spread) {
-        if (!emitCall(pn->getOp(), argc, pn))
-            return false;
+        if (pn->getOp() == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
+            if (!emitCall(JSOP_CALL_IGNORES_RV, argc, pn))
+                return false;
+            checkTypeSet(JSOP_CALL_IGNORES_RV);
+        } else {
+            if (!emitCall(pn->getOp(), argc, pn))
+                return false;
+            checkTypeSet(pn->getOp());
+        }
     } else {
         if (!emit1(pn->getOp()))
             return false;
-    }
-    checkTypeSet(pn->getOp());
+        checkTypeSet(pn->getOp());
+    }
     if (pn->isOp(JSOP_EVAL) ||
         pn->isOp(JSOP_STRICTEVAL) ||
         pn->isOp(JSOP_SPREADEVAL) ||
         pn->isOp(JSOP_STRICTSPREADEVAL))
     {
         uint32_t lineNum = parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
         if (!emitUint32Operand(JSOP_LINENO, lineNum))
             return false;
@@ -9345,22 +9355,23 @@ BytecodeEmitter::emitLogical(ParseNode* 
         return false;
 
     if (!emitJumpTargetAndPatch(jump))
         return false;
     return true;
 }
 
 bool
-BytecodeEmitter::emitSequenceExpr(ParseNode* pn)
+BytecodeEmitter::emitSequenceExpr(ParseNode* pn,
+                                  ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
     for (ParseNode* child = pn->pn_head; ; child = child->pn_next) {
         if (!updateSourceCoordNotes(child->pn_pos.begin))
             return false;
-        if (!emitTree(child))
+        if (!emitTree(child, child->pn_next ? ValueUsage::IgnoreValue : valueUsage))
             return false;
         if (!child->pn_next)
             break;
         if (!emit1(JSOP_POP))
             return false;
     }
     return true;
 }
@@ -9413,33 +9424,34 @@ BytecodeEmitter::emitLabeledStatement(co
 
     if (!controlInfo.patchBreaks(this))
         return false;
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional)
+BytecodeEmitter::emitConditionalExpression(ConditionalExpression& conditional,
+                                           ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
     /* Emit the condition, then branch if false to the else part. */
     if (!emitTree(&conditional.condition()))
         return false;
 
     IfThenElseEmitter ifThenElse(this);
     if (!ifThenElse.emitCond())
         return false;
 
-    if (!emitTreeInBranch(&conditional.thenExpression()))
+    if (!emitTreeInBranch(&conditional.thenExpression(), valueUsage))
         return false;
 
     if (!ifThenElse.emitElse())
         return false;
 
-    if (!emitTreeInBranch(&conditional.elseExpression()))
+    if (!emitTreeInBranch(&conditional.elseExpression(), valueUsage))
         return false;
 
     if (!ifThenElse.emitEnd())
         return false;
     MOZ_ASSERT(ifThenElse.pushed() == 1);
 
     return true;
 }
@@ -10307,17 +10319,18 @@ BytecodeEmitter::emitClass(ParseNode* pn
     }
 
     MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
+BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
+                          EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */)
 {
     if (!CheckRecursionLimit(cx))
         return false;
 
     EmitLevelManager elm(this);
 
     /* Emit notes to tell the current bytecode's source line number.
        However, a couple trees require special treatment; see the
@@ -10439,17 +10452,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         break;
 
       case PNK_LABEL:
         if (!emitLabeledStatement(&pn->as<LabeledStatement>()))
             return false;
         break;
 
       case PNK_COMMA:
-        if (!emitSequenceExpr(pn))
+        if (!emitSequenceExpr(pn, valueUsage))
             return false;
         break;
 
       case PNK_ASSIGN:
       case PNK_ADDASSIGN:
       case PNK_SUBASSIGN:
       case PNK_BITORASSIGN:
       case PNK_BITXORASSIGN:
@@ -10461,17 +10474,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case PNK_DIVASSIGN:
       case PNK_MODASSIGN:
       case PNK_POWASSIGN:
         if (!emitAssignment(pn->pn_left, pn->getOp(), pn->pn_right))
             return false;
         break;
 
       case PNK_CONDITIONAL:
-        if (!emitConditionalExpression(pn->as<ConditionalExpression>()))
+        if (!emitConditionalExpression(pn->as<ConditionalExpression>(), valueUsage))
             return false;
         break;
 
       case PNK_OR:
       case PNK_AND:
         if (!emitLogical(pn))
             return false;
         break;
@@ -10574,17 +10587,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         }
         break;
 
       case PNK_NEW:
       case PNK_TAGGED_TEMPLATE:
       case PNK_CALL:
       case PNK_GENEXP:
       case PNK_SUPERCALL:
-        if (!emitCallOrNew(pn))
+        if (!emitCallOrNew(pn, valueUsage))
             return false;
         break;
 
       case PNK_LEXICALSCOPE:
         if (!emitLexicalScope(pn))
             return false;
         break;
 
@@ -10732,22 +10745,23 @@ BytecodeEmitter::emitTree(ParseNode* pn,
     if (emitLevel == 1) {
         if (!updateSourceCoordNotes(pn->pn_pos.end))
             return false;
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitTreeInBranch(ParseNode* pn)
+BytecodeEmitter::emitTreeInBranch(ParseNode* pn,
+                                  ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
     // Code that may be conditionally executed always need their own TDZ
     // cache.
     TDZCheckCache tdzCache(this);
-    return emitTree(pn);
+    return emitTree(pn, valueUsage);
 }
 
 static bool
 AllocSrcNote(JSContext* cx, SrcNotesVector& notes, unsigned* index)
 {
     // Start it off moderately large to avoid repeated resizings early on.
     // ~99% of cases fit within 256 bytes.
     if (notes.capacity() == 0 && !notes.reserve(256))
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -162,16 +162,21 @@ struct JumpList {
     // Add a jump instruction to the list.
     void push(jsbytecode* code, ptrdiff_t jumpOffset);
 
     // Patch all jump instructions in this list to jump to `target`.  This
     // clobbers the list.
     void patchAll(jsbytecode* code, JumpTarget target);
 };
 
+enum class ValueUsage {
+    WantValue,
+    IgnoreValue
+};
+
 struct MOZ_STACK_CLASS BytecodeEmitter
 {
     class TDZCheckCache;
     class NestableControl;
     class EmitterScope;
 
     SharedContext* const sc;      /* context shared between parsing and bytecode generation */
 
@@ -425,20 +430,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     // Control whether emitTree emits a line number note.
     enum EmitLineNumberNote {
         EMIT_LINENOTE,
         SUPPRESS_LINENOTE
     };
 
     // Emit code for the tree rooted at pn.
-    MOZ_MUST_USE bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+    MOZ_MUST_USE bool emitTree(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue,
+                               EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
 
     // Emit code for the tree rooted at pn with its own TDZ cache.
-    MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn);
+    MOZ_MUST_USE bool emitTreeInBranch(ParseNode* pn,
+                                       ValueUsage valueUsage = ValueUsage::WantValue);
 
     // Emit global, eval, or module code for tree rooted at body. Always
     // encompasses the entire source.
     MOZ_MUST_USE bool emitScript(ParseNode* body);
 
     // Emit function code for the tree rooted at body.
     MOZ_MUST_USE bool emitFunctionScript(ParseNode* body);
 
@@ -726,26 +733,28 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     // |op| must be JSOP_TYPEOF or JSOP_TYPEOFEXPR.
     MOZ_MUST_USE bool emitTypeof(ParseNode* node, JSOp op);
 
     MOZ_MUST_USE bool emitUnary(ParseNode* pn);
     MOZ_MUST_USE bool emitRightAssociative(ParseNode* pn);
     MOZ_MUST_USE bool emitLeftAssociative(ParseNode* pn);
     MOZ_MUST_USE bool emitLogical(ParseNode* pn);
-    MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn);
+    MOZ_MUST_USE bool emitSequenceExpr(ParseNode* pn,
+                                       ValueUsage valueUsage = ValueUsage::WantValue);
 
     MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(ParseNode* pn);
 
-    MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional);
+    MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional,
+                                                ValueUsage valueUsage = ValueUsage::WantValue);
 
     MOZ_MUST_USE bool isRestParameter(ParseNode* pn, bool* result);
     MOZ_MUST_USE bool emitOptimizeSpread(ParseNode* arg0, JumpList* jmp, bool* emitted);
 
-    MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn);
+    MOZ_MUST_USE bool emitCallOrNew(ParseNode* pn, ValueUsage valueUsage = ValueUsage::WantValue);
     MOZ_MUST_USE bool emitSelfHostedCallFunction(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedResumeGenerator(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedForceInterpreter(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedAllowContentIter(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(ParseNode* pn);
 
     MOZ_MUST_USE bool emitComprehensionFor(ParseNode* compFor);
     MOZ_MUST_USE bool emitComprehensionForIn(ParseNode* pn);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -3295,16 +3295,22 @@ BaselineCompiler::emitSpreadCall()
 
 bool
 BaselineCompiler::emit_JSOP_CALL()
 {
     return emitCall();
 }
 
 bool
+BaselineCompiler::emit_JSOP_CALL_IGNORES_RV()
+{
+    return emitCall();
+}
+
+bool
 BaselineCompiler::emit_JSOP_CALLITER()
 {
     return emitCall();
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEW()
 {
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -155,16 +155,17 @@ namespace jit {
     _(JSOP_SETARG)             \
     _(JSOP_CHECKLEXICAL)       \
     _(JSOP_INITLEXICAL)        \
     _(JSOP_INITGLEXICAL)       \
     _(JSOP_CHECKALIASEDLEXICAL) \
     _(JSOP_INITALIASEDLEXICAL) \
     _(JSOP_UNINITIALIZED)      \
     _(JSOP_CALL)               \
+    _(JSOP_CALL_IGNORES_RV)    \
     _(JSOP_CALLITER)           \
     _(JSOP_FUNCALL)            \
     _(JSOP_FUNAPPLY)           \
     _(JSOP_NEW)                \
     _(JSOP_EVAL)               \
     _(JSOP_STRICTEVAL)         \
     _(JSOP_SPREADCALL)         \
     _(JSOP_SPREADNEW)          \
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/BaselineIC.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/TemplateLib.h"
 
+#include "jsfriendapi.h"
+#include "jsfun.h"
 #include "jslibmath.h"
 #include "jstypes.h"
 
 #include "builtin/Eval.h"
 #include "builtin/SIMD.h"
 #include "gc/Policy.h"
 #include "jit/BaselineCacheIRCompiler.h"
 #include "jit/BaselineDebugModeOSR.h"
@@ -2191,21 +2193,27 @@ TryAttachCallStub(JSContext* cx, ICCall_
                 return false;
             if (skipAttach) {
                 *handled = true;
                 return true;
             }
             MOZ_ASSERT_IF(templateObject, !templateObject->group()->maybePreliminaryObjects());
         }
 
+        bool ignoresReturnValue = false;
+        if (op == JSOP_CALL_IGNORES_RV && fun->isNative()) {
+            const JSJitInfo* jitInfo = fun->jitInfo();
+            ignoresReturnValue = jitInfo && jitInfo->type() == JSJitInfo::IgnoresReturnValueNative;
+        }
+
         JitSpew(JitSpew_BaselineIC, "  Generating Call_Native stub (fun=%p, cons=%s, spread=%s)",
                 fun.get(), constructing ? "yes" : "no", isSpread ? "yes" : "no");
         ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
-                                         fun, templateObject, constructing, isSpread,
-                                         script->pcToOffset(pc));
+                                         fun, templateObject, constructing, ignoresReturnValue,
+                                         isSpread, script->pcToOffset(pc));
         ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
         if (!newStub)
             return false;
 
         stub->addNewStub(newStub);
         *handled = true;
         return true;
     }
@@ -2293,22 +2301,24 @@ DoCallFallback(JSContext* cx, BaselineFr
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "Call(%s)", CodeName[op]);
 
     MOZ_ASSERT(argc == GET_ARGC(pc));
     bool constructing = (op == JSOP_NEW);
+    bool ignoresReturnValue = (op == JSOP_CALL_IGNORES_RV);
 
     // Ensure vp array is rooted - we may GC in here.
     size_t numValues = argc + 2 + constructing;
     AutoArrayRooter vpRoot(cx, numValues, vp);
 
-    CallArgs callArgs = CallArgsFromSp(argc + constructing, vp + numValues, constructing);
+    CallArgs callArgs = CallArgsFromSp(argc + constructing, vp + numValues, constructing,
+                                       ignoresReturnValue);
     RootedValue callee(cx, vp[0]);
 
     // Handle funapply with JSOP_ARGUMENTS
     if (op == JSOP_FUNAPPLY && argc == 2 && callArgs[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) {
         if (!GuardFunApplyArgumentsOptimization(cx, frame, callArgs))
             return false;
     }
 
@@ -2328,16 +2338,17 @@ DoCallFallback(JSContext* cx, BaselineFr
         res.set(callArgs.rval());
     } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) &&
                frame->environmentChain()->global().valueIsEval(callee))
     {
         if (!DirectEval(cx, callArgs.get(0), res))
             return false;
     } else {
         MOZ_ASSERT(op == JSOP_CALL ||
+                   op == JSOP_CALL_IGNORES_RV ||
                    op == JSOP_CALLITER ||
                    op == JSOP_FUNCALL ||
                    op == JSOP_FUNAPPLY ||
                    op == JSOP_EVAL ||
                    op == JSOP_STRICTEVAL);
         if (op == JSOP_CALLITER && callee.isPrimitive()) {
             MOZ_ASSERT(argc == 0, "thisv must be on top of the stack");
             ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, callArgs.thisv(), nullptr);
@@ -3379,17 +3390,22 @@ ICCall_Native::Compiler::generateStubCod
     masm.passABIArg(vpReg);
 
 #ifdef JS_SIMULATOR
     // The simulator requires VM calls to be redirected to a special swi
     // instruction to handle them, so we store the redirected pointer in the
     // stub and use that instead of the original one.
     masm.callWithABI(Address(ICStubReg, ICCall_Native::offsetOfNative()));
 #else
-    masm.callWithABI(Address(callee, JSFunction::offsetOfNativeOrScript()));
+    if (ignoresReturnValue_) {
+        masm.loadPtr(Address(callee, JSFunction::offsetOfJitInfo()), callee);
+        masm.callWithABI(Address(callee, JSJitInfo::offsetOfIgnoresReturnValueNative()));
+    } else {
+        masm.callWithABI(Address(callee, JSFunction::offsetOfNativeOrScript()));
+    }
 #endif
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel());
 
     // Load the return value into R0.
     masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), R0);
 
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -663,16 +663,17 @@ class ICSetProp_Fallback : public ICFall
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICSetProp_Fallback>(space, getStubCode());
         }
     };
 };
 
 // Call
 //      JSOP_CALL
+//      JSOP_CALL_IGNORES_RV
 //      JSOP_FUNAPPLY
 //      JSOP_FUNCALL
 //      JSOP_NEW
 //      JSOP_SPREADCALL
 //      JSOP_SPREADNEW
 //      JSOP_SPREADEVAL
 
 class ICCallStubCompiler : public ICStubCompiler
@@ -933,36 +934,39 @@ class ICCall_Native : public ICMonitored
     }
 #endif
 
     // Compiler for this stub kind.
     class Compiler : public ICCallStubCompiler {
       protected:
         ICStub* firstMonitorStub_;
         bool isConstructing_;
+        bool ignoresReturnValue_;
         bool isSpread_;
         RootedFunction callee_;
         RootedObject templateObject_;
         uint32_t pcOffset_;
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
 
         virtual int32_t getKey() const {
             return static_cast<int32_t>(engine_) |
                   (static_cast<int32_t>(kind) << 1) |
-                  (static_cast<int32_t>(isConstructing_) << 17) |
-                  (static_cast<int32_t>(isSpread_) << 18);
+                  (static_cast<int32_t>(isSpread_) << 17) |
+                  (static_cast<int32_t>(isConstructing_) << 18) |
+                  (static_cast<int32_t>(ignoresReturnValue_) << 19);
         }
 
       public:
         Compiler(JSContext* cx, ICStub* firstMonitorStub,
                  HandleFunction callee, HandleObject templateObject,
-                 bool isConstructing, bool isSpread, uint32_t pcOffset)
+                 bool isConstructing, bool ignoresReturnValue, bool isSpread, uint32_t pcOffset)
           : ICCallStubCompiler(cx, ICStub::Call_Native),
             firstMonitorStub_(firstMonitorStub),
             isConstructing_(isConstructing),
+            ignoresReturnValue_(ignoresReturnValue),
             isSpread_(isSpread),
             callee_(cx, callee),
             templateObject_(cx, templateObject),
             pcOffset_(pcOffset)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICCall_Native>(space, getStubCode(), firstMonitorStub_, callee_,
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3926,17 +3926,23 @@ CodeGenerator::visitCallNative(LCallNati
 
     emitTracelogStartEvent(TraceLogger_Call);
 
     // Construct and execute call.
     masm.setupUnalignedABICall(tempReg);
     masm.passABIArg(argContextReg);
     masm.passABIArg(argUintNReg);
     masm.passABIArg(argVpReg);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, target->native()));
+    JSNative native = target->native();
+    if (call->ignoresReturnValue()) {
+        const JSJitInfo* jitInfo = target->jitInfo();
+        if (jitInfo && jitInfo->type() == JSJitInfo::IgnoresReturnValueNative)
+            native = jitInfo->ignoresReturnValueMethod;
+    }
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, native));
 
     emitTracelogStopEvent(TraceLogger_Call);
 
     // Test for failure.
     masm.branchIfFalseBool(ReturnReg, masm.failureLabel());
 
     // Load the outparam vp[0] into output register(s).
     masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), JSReturnOperand);
@@ -4083,30 +4089,33 @@ static const VMFunction GetIntrinsicValu
 
 void
 CodeGenerator::visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir)
 {
     pushArg(ImmGCPtr(lir->mir()->name()));
     callVM(GetIntrinsicValueInfo, lir);
 }
 
-typedef bool (*InvokeFunctionFn)(JSContext*, HandleObject, bool, uint32_t, Value*, MutableHandleValue);
+typedef bool (*InvokeFunctionFn)(JSContext*, HandleObject, bool, bool, uint32_t, Value*,
+                                 MutableHandleValue);
 static const VMFunction InvokeFunctionInfo =
     FunctionInfo<InvokeFunctionFn>(InvokeFunction, "InvokeFunction");
 
 void
 CodeGenerator::emitCallInvokeFunction(LInstruction* call, Register calleereg,
-                                      bool constructing, uint32_t argc, uint32_t unusedStack)
+                                      bool constructing, bool ignoresReturnValue,
+                                      uint32_t argc, uint32_t unusedStack)
 {
     // Nestle %esp up to the argument vector.
     // Each path must account for framePushed_ separately, for callVM to be valid.
     masm.freeStack(unusedStack);
 
     pushArg(masm.getStackPointer()); // argv.
     pushArg(Imm32(argc));            // argc.
+    pushArg(Imm32(ignoresReturnValue));
     pushArg(Imm32(constructing));    // constructing.
     pushArg(calleereg);              // JSFunction*.
 
     callVM(InvokeFunctionInfo, call);
 
     // Un-nestle %esp from the argument vector. No prefix was pushed.
     masm.reserveStack(unusedStack);
 }
@@ -4183,18 +4192,18 @@ CodeGenerator::visitCallGeneric(LCallGen
     // Increment to remove IonFramePrefix; decrement to fill FrameSizeClass.
     // The return address has already been removed from the Ion frame.
     int prefixGarbage = sizeof(JitFrameLayout) - sizeof(void*);
     masm.adjustStack(prefixGarbage - unusedStack);
     masm.jump(&end);
 
     // Handle uncompiled or native functions.
     masm.bind(&invoke);
-    emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->numActualArgs(),
-                           unusedStack);
+    emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->ignoresReturnValue(),
+                           call->numActualArgs(), unusedStack);
 
     masm.bind(&end);
 
     // If the return value of the constructing function is Primitive,
     // replace the return value with the Object from CreateThis.
     if (call->mir()->isConstructing()) {
         Label notPrimitive;
         masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, &notPrimitive);
@@ -4239,17 +4248,18 @@ CodeGenerator::visitCallKnown(LCallKnown
     DebugOnly<unsigned> numNonArgsOnStack = 1 + call->isConstructing();
     MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - numNonArgsOnStack);
 
     MOZ_ASSERT_IF(call->isConstructing(), target->isConstructor());
 
     masm.checkStackAlignment();
 
     if (target->isClassConstructor() && !call->isConstructing()) {
-        emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->numActualArgs(), unusedStack);
+        emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->ignoresReturnValue(),
+                               call->numActualArgs(), unusedStack);
         return;
     }
 
     MOZ_ASSERT_IF(target->isClassConstructor(), call->isConstructing());
 
     // The calleereg is known to be a non-native function, but might point to
     // a LazyScript instead of a JSScript.
     masm.branchIfFunctionHasNoScript(calleereg, &uncompiled);
@@ -4283,17 +4293,18 @@ CodeGenerator::visitCallKnown(LCallKnown
     masm.adjustStack(prefixGarbage - unusedStack);
     masm.jump(&end);
 
     // Handle uncompiled functions.
     masm.bind(&uncompiled);
     if (call->isConstructing() && target->nargs() > call->numActualArgs())
         emitCallInvokeFunctionShuffleNewTarget(call, calleereg, target->nargs(), unusedStack);
     else
-        emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->numActualArgs(), unusedStack);
+        emitCallInvokeFunction(call, calleereg, call->isConstructing(), call->ignoresReturnValue(),
+                               call->numActualArgs(), unusedStack);
 
     masm.bind(&end);
 
     // If the return value of the constructing function is Primitive,
     // replace the return value with the Object from CreateThis.
     if (call->mir()->isConstructing()) {
         Label notPrimitive;
         masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, &notPrimitive);
@@ -4310,16 +4321,17 @@ CodeGenerator::emitCallInvokeFunction(T*
     MOZ_ASSERT(objreg != extraStackSize);
 
     // Push the space used by the arguments.
     masm.moveStackPtrTo(objreg);
     masm.Push(extraStackSize);
 
     pushArg(objreg);                           // argv.
     pushArg(ToRegister(apply->getArgc()));     // argc.
+    pushArg(Imm32(false));                     // ignoresReturnValue.
     pushArg(Imm32(false));                     // isConstrucing.
     pushArg(ToRegister(apply->getFunction())); // JSFunction*.
 
     // This specialization og callVM restore the extraStackSize after the call.
     callVM(InvokeFunctionInfo, apply, &extraStackSize);
 
     masm.Pop(extraStackSize);
 }
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -163,18 +163,18 @@ class CodeGenerator final : public CodeG
     void visitPostWriteBarrierO(LPostWriteBarrierO* lir);
     void visitPostWriteElementBarrierO(LPostWriteElementBarrierO* lir);
     void visitPostWriteBarrierV(LPostWriteBarrierV* lir);
     void visitPostWriteElementBarrierV(LPostWriteElementBarrierV* lir);
     void visitOutOfLineCallPostWriteBarrier(OutOfLineCallPostWriteBarrier* ool);
     void visitOutOfLineCallPostWriteElementBarrier(OutOfLineCallPostWriteElementBarrier* ool);
     void visitCallNative(LCallNative* call);
     void emitCallInvokeFunction(LInstruction* call, Register callereg,
-                                bool isConstructing, uint32_t argc,
-                                uint32_t unusedStack);
+                                bool isConstructing, bool ignoresReturnValue,
+                                uint32_t argc, uint32_t unusedStack);
     void visitCallGeneric(LCallGeneric* call);
     void emitCallInvokeFunctionShuffleNewTarget(LCallKnown *call,
                                                 Register calleeReg,
                                                 uint32_t numFormals,
                                                 uint32_t unusedStack);
     void visitCallKnown(LCallKnown* call);
     template<typename T> void emitApplyGeneric(T* apply);
     template<typename T> void emitCallInvokeFunction(T* apply, Register extraStackSize);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2005,20 +2005,22 @@ IonBuilder::inspectOpcode(JSOp op)
 
       case JSOP_FUNCALL:
         return jsop_funcall(GET_ARGC(pc));
 
       case JSOP_FUNAPPLY:
         return jsop_funapply(GET_ARGC(pc));
 
       case JSOP_CALL:
+      case JSOP_CALL_IGNORES_RV:
       case JSOP_CALLITER:
       case JSOP_NEW:
       case JSOP_SUPERCALL:
-        MOZ_TRY(jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL));
+        MOZ_TRY(jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL,
+                          (JSOp)*pc == JSOP_CALL_IGNORES_RV));
         if (op == JSOP_CALLITER) {
             if (!outermostBuilder()->iterators_.append(current->peek(-1)))
                 return abort(AbortReason::Alloc);
         }
         return Ok();
 
       case JSOP_EVAL:
       case JSOP_STRICTEVAL:
@@ -4267,17 +4269,17 @@ AbortReasonOr<Ok>
 IonBuilder::inlineGenericFallback(JSFunction* target, CallInfo& callInfo, MBasicBlock* dispatchBlock)
 {
     // Generate a new block with all arguments on-stack.
     MBasicBlock* fallbackBlock;
     MOZ_TRY_VAR(fallbackBlock, newBlock(dispatchBlock, pc));
     graph().addBlock(fallbackBlock);
 
     // Create a new CallInfo to track modified state within this block.
-    CallInfo fallbackInfo(alloc(), callInfo.constructing());
+    CallInfo fallbackInfo(alloc(), callInfo.constructing(), callInfo.ignoresReturnValue());
     if (!fallbackInfo.init(callInfo))
         return abort(AbortReason::Alloc);
     fallbackInfo.popFormals(fallbackBlock);
 
     // Generate an MCall, which uses stateful |current|.
     MOZ_TRY(setCurrentAndSpecializePhis(fallbackBlock));
     MOZ_TRY(makeCall(target, fallbackInfo));
 
@@ -4304,17 +4306,17 @@ IonBuilder::inlineObjectGroupFallback(Ca
     MOZ_ASSERT_IF(callInfo.fun()->isTypeBarrier(), cache->hasOneUse());
 
     // This means that no resume points yet capture the MGetPropertyCache,
     // so everything from the MGetPropertyCache up until the call is movable.
     // We now move the MGetPropertyCache and friends into a fallback path.
     MOZ_ASSERT(cache->idempotent());
 
     // Create a new CallInfo to track modified state within the fallback path.
-    CallInfo fallbackInfo(alloc(), callInfo.constructing());
+    CallInfo fallbackInfo(alloc(), callInfo.constructing(), callInfo.ignoresReturnValue());
     if (!fallbackInfo.init(callInfo))
         return abort(AbortReason::Alloc);
 
     // Capture stack prior to the call operation. This captures the function.
     MResumePoint* preCallResumePoint =
         MResumePoint::New(alloc(), dispatchBlock, pc, MResumePoint::ResumeAt);
     if (!preCallResumePoint)
         return abort(AbortReason::Alloc);
@@ -4480,17 +4482,17 @@ IonBuilder::inlineCalls(CallInfo& callIn
         dispatchBlock->add(funcDef);
 
         // Use the inlined callee in the inline resume point and on stack.
         int funIndex = inlineBlock->entryResumePoint()->stackDepth() - callInfo.numFormals();
         inlineBlock->entryResumePoint()->replaceOperand(funIndex, funcDef);
         inlineBlock->rewriteSlot(funIndex, funcDef);
 
         // Create a new CallInfo to track modified state within the inline block.
-        CallInfo inlineInfo(alloc(), callInfo.constructing());
+        CallInfo inlineInfo(alloc(), callInfo.constructing(), callInfo.ignoresReturnValue());
         if (!inlineInfo.init(callInfo))
             return abort(AbortReason::Alloc);
         inlineInfo.popFormals(inlineBlock);
         inlineInfo.setFun(funcDef);
 
         if (maybeCache) {
             // Assign the 'this' value a TypeSet specialized to the groups that
             // can generate this inlining target.
@@ -4923,17 +4925,18 @@ IonBuilder::jsop_funcall(uint32_t argc)
 
     int calleeDepth = -((int)argc + 2);
     int funcDepth = -((int)argc + 1);
 
     // If |Function.prototype.call| may be overridden, don't optimize callsite.
     TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
     JSFunction* native = getSingleCallTarget(calleeTypes);
     if (!native || !native->isNative() || native->native() != &fun_call) {
-        CallInfo callInfo(alloc(), false);
+        CallInfo callInfo(alloc(), /* constructing = */ false,
+                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
         if (!callInfo.init(current, argc))
             return abort(AbortReason::Alloc);
         return makeCall(native, callInfo);
     }
     current->peek(calleeDepth)->setImplicitlyUsedUnchecked();
 
     // Extract call target.
     TemporaryTypeSet* funTypes = current->peek(funcDepth)->resultTypeSet();
@@ -4948,17 +4951,18 @@ IonBuilder::jsop_funcall(uint32_t argc)
     // Pushing is safe here, since one stack slot has been removed.
     if (zeroArguments) {
         pushConstant(UndefinedValue());
     } else {
         // |this| becomes implicit in the call.
         argc -= 1;
     }
 
-    CallInfo callInfo(alloc(), false);
+    CallInfo callInfo(alloc(), /* constructing = */ false,
+                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
     if (!callInfo.init(current, argc))
         return abort(AbortReason::Alloc);
 
     // Try to inline the call.
     if (!zeroArguments) {
         InliningDecision decision = makeInliningDecision(target, callInfo);
         switch (decision) {
           case InliningDecision_Error:
@@ -4984,17 +4988,18 @@ IonBuilder::jsop_funcall(uint32_t argc)
 AbortReasonOr<Ok>
 IonBuilder::jsop_funapply(uint32_t argc)
 {
     int calleeDepth = -((int)argc + 2);
 
     TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
     JSFunction* native = getSingleCallTarget(calleeTypes);
     if (argc != 2 || info().analysisMode() == Analysis_ArgumentsUsage) {
-        CallInfo callInfo(alloc(), false);
+        CallInfo callInfo(alloc(), /* constructing = */ false,
+                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
         if (!callInfo.init(current, argc))
             return abort(AbortReason::Alloc);
         return makeCall(native, callInfo);
     }
 
     // Disable compilation if the second argument to |apply| cannot be guaranteed
     // to be either definitely |arguments| or definitely not |arguments|.
     MDefinition* argument = current->peek(-1);
@@ -5013,17 +5018,18 @@ IonBuilder::jsop_funapply(uint32_t argc)
             objTypes &&
             objTypes->getKnownClass(constraints()) == &ArrayObject::class_ &&
             !objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW) &&
             ElementAccessIsPacked(constraints(), argument))
         {
             return jsop_funapplyarray(argc);
         }
 
-        CallInfo callInfo(alloc(), false);
+        CallInfo callInfo(alloc(), /* constructing = */ false,
+                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
         if (!callInfo.init(current, argc))
             return abort(AbortReason::Alloc);
         return makeCall(native, callInfo);
     }
 
     if ((!native || !native->isNative() ||
         native->native() != fun_apply) &&
         info().analysisMode() != Analysis_DefiniteProperties)
@@ -5120,17 +5126,18 @@ IonBuilder::jsop_funapplyarguments(uint3
     }
 
     // When inlining we have the arguments the function gets called with
     // and can optimize even more, by just calling the functions with the args.
     // We also try this path when doing the definite properties analysis, as we
     // can inline the apply() target and don't care about the actual arguments
     // that were passed in.
 
-    CallInfo callInfo(alloc(), false);
+    CallInfo callInfo(alloc(), /* constructing = */ false,
+                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
 
     // Vp
     MDefinition* vp = current->pop();
     vp->setImplicitlyUsedUnchecked();
 
     // Arguments
     if (inliningDepth_) {
         if (!callInfo.setArgs(inlineCallInfo_->argv()))
@@ -5165,17 +5172,17 @@ IonBuilder::jsop_funapplyarguments(uint3
                 return Ok();
         }
     }
 
     return makeCall(target, callInfo);
 }
 
 AbortReasonOr<Ok>
-IonBuilder::jsop_call(uint32_t argc, bool constructing)
+IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue)
 {
     startTrackingOptimizations();
 
     // If this call has never executed, try to seed the observed type set
     // based on how the call result is used.
     TemporaryTypeSet* observed = bytecodeTypes(pc);
     if (observed->empty()) {
         if (BytecodeFlowsToBitop(pc)) {
@@ -5191,17 +5198,17 @@ IonBuilder::jsop_call(uint32_t argc, boo
     int calleeDepth = -((int)argc + 2 + constructing);
 
     // Acquire known call target if existent.
     ObjectVector targets(alloc());
     TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
     if (calleeTypes)
         MOZ_TRY(getPolyCallTargets(calleeTypes, constructing, targets, 4));
 
-    CallInfo callInfo(alloc(), constructing);
+    CallInfo callInfo(alloc(), constructing, ignoresReturnValue);
     if (!callInfo.init(current, argc))
         return abort(AbortReason::Alloc);
 
     // Try inlining
     InliningStatus status;
     MOZ_TRY_VAR(status, inlineCallsite(targets, callInfo));
     if (status == InliningStatus_Inlined)
         return Ok();
@@ -5328,17 +5335,18 @@ IonBuilder::makeCallHelper(JSFunction* t
             thisTypes->getKnownMIRType() == MIRType::Object &&
             thisTypes->isDOMClass(constraints()))
         {
             MOZ_TRY_VAR(isDOMCall, testShouldDOMCall(thisTypes, target, JSJitInfo::Method));
         }
     }
 
     MCall* call = MCall::New(alloc(), target, targetArgs + 1 + callInfo.constructing(),
-                             callInfo.argc(), callInfo.constructing(), isDOMCall);
+                             callInfo.argc(), callInfo.constructing(),
+                             callInfo.ignoresReturnValue(), isDOMCall);
     if (!call)
         return abort(AbortReason::Alloc);
 
     if (callInfo.constructing())
         call->addArg(targetArgs + 1, callInfo.getNewTarget());
 
     // Explicitly pad any missing arguments with |undefined|.
     // This permits skipping the argumentsRectifier.
@@ -5426,33 +5434,34 @@ AbortReasonOr<Ok>
 IonBuilder::jsop_eval(uint32_t argc)
 {
     int calleeDepth = -((int)argc + 2);
     TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
 
     // Emit a normal call if the eval has never executed. This keeps us from
     // disabling compilation for the script when testing with --ion-eager.
     if (calleeTypes && calleeTypes->empty())
-        return jsop_call(argc, /* constructing = */ false);
+        return jsop_call(argc, /* constructing = */ false, false);
 
     JSFunction* singleton = getSingleCallTarget(calleeTypes);
     if (!singleton)
         return abort(AbortReason::Disable, "No singleton callee for eval()");
 
     if (script()->global().valueIsEval(ObjectValue(*singleton))) {
         if (argc != 1)
             return abort(AbortReason::Disable, "Direct eval with more than one argument");
 
         if (!info().funMaybeLazy())
             return abort(AbortReason::Disable, "Direct eval in global code");
 
         if (info().funMaybeLazy()->isArrow())
             return abort(AbortReason::Disable, "Direct eval from arrow function");
 
-        CallInfo callInfo(alloc(), /* constructing = */ false);
+        CallInfo callInfo(alloc(), /* constructing = */ false,
+                          /* ignoresReturnValue = */ BytecodeIsPopped(pc));
         if (!callInfo.init(current, argc))
             return abort(AbortReason::Alloc);
         callInfo.setImplicitlyUsedUnchecked();
 
         callInfo.fun()->setImplicitlyUsedUnchecked();
 
         MDefinition* envChain = current->environmentChain();
         MDefinition* string = callInfo.getArg(0);
@@ -5480,17 +5489,18 @@ IonBuilder::jsop_eval(uint32_t argc)
             if (StringEqualsAscii(atom, "()")) {
                 MDefinition* name = string->getOperand(0);
                 MInstruction* dynamicName = MGetDynamicName::New(alloc(), envChain, name);
                 current->add(dynamicName);
 
                 current->push(dynamicName);
                 current->push(constant(UndefinedValue())); // thisv
 
-                CallInfo evalCallInfo(alloc(), /* constructing = */ false);
+                CallInfo evalCallInfo(alloc(), /* constructing = */ false,
+                                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
                 if (!evalCallInfo.init(current, /* argc = */ 0))
                     return abort(AbortReason::Alloc);
 
                 return makeCall(nullptr, evalCallInfo);
             }
         }
 
         MInstruction* ins = MCallDirectEval::New(alloc(), envChain, string,
@@ -5498,17 +5508,17 @@ IonBuilder::jsop_eval(uint32_t argc)
         current->add(ins);
         current->push(ins);
 
         TemporaryTypeSet* types = bytecodeTypes(pc);
         MOZ_TRY(resumeAfter(ins));
         return pushTypeBarrier(ins, types, BarrierKind::TypeSet);
     }
 
-    return jsop_call(argc, /* constructing = */ false);
+    return jsop_call(argc, /* constructing = */ false, false);
 }
 
 AbortReasonOr<Ok>
 IonBuilder::jsop_compare(JSOp op)
 {
     MDefinition* right = current->pop();
     MDefinition* left = current->pop();
 
@@ -5752,18 +5762,18 @@ IonBuilder::compareTrySharedStub(bool* e
     return Ok();
 }
 
 static bool
 IsCallOpcode(JSOp op)
 {
     // TODO: Support tracking optimizations for inlining a call and regular
     // optimization tracking at the same time.
-    return op == JSOP_CALL || op == JSOP_CALLITER || op == JSOP_NEW || op == JSOP_SUPERCALL ||
-           op == JSOP_EVAL || op == JSOP_STRICTEVAL;
+    return op == JSOP_CALL || op == JSOP_CALL_IGNORES_RV || op == JSOP_CALLITER || op == JSOP_NEW ||
+           op == JSOP_SUPERCALL || op == JSOP_EVAL || op == JSOP_STRICTEVAL;
 }
 
 AbortReasonOr<Ok>
 IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, uint32_t length)
 {
     MOZ_ASSERT(*emitted == false);
 
     if (!IsCallOpcode(JSOp(*pc)))
@@ -10844,17 +10854,18 @@ IonBuilder::getPropTryCommonGetter(bool*
 
     // Make sure there's enough room
     if (!current->ensureHasSlots(2))
         return abort(AbortReason::Alloc);
     current->push(constant(ObjectValue(*commonGetter)));
 
     current->push(obj);
 
-    CallInfo callInfo(alloc(), false);
+    CallInfo callInfo(alloc(), /* constructing = */ false,
+                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
     if (!callInfo.init(current, 0))
         return abort(AbortReason::Alloc);
 
     if (commonGetter->isNative()) {
         InliningStatus status;
         MOZ_TRY_VAR(status, inlineNativeGetter(callInfo, commonGetter));
         switch (status) {
           case InliningStatus_WarmUpCountTooLow:
@@ -11370,17 +11381,18 @@ IonBuilder::setPropTryCommonSetter(bool*
         return abort(AbortReason::Alloc);
 
     current->push(constant(ObjectValue(*commonSetter)));
     current->push(obj);
     current->push(value);
 
     // Call the setter. Note that we have to push the original value, not
     // the setter's return value.
-    CallInfo callInfo(alloc(), false);
+    CallInfo callInfo(alloc(), /* constructing = */ false,
+                      /* ignoresReturnValue = */ BytecodeIsPopped(pc));
     if (!callInfo.init(current, 1))
         return abort(AbortReason::Alloc);
 
     // Ensure that we know we are calling a setter in case we inline it.
     callInfo.markAsSetter();
 
     // Inline the setter if we can.
     if (commonSetter->isInterpreted()) {
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -499,17 +499,17 @@ class IonBuilder
     AbortReasonOr<Ok> jsop_notearg();
     AbortReasonOr<Ok> jsop_throwsetconst();
     AbortReasonOr<Ok> jsop_checklexical();
     AbortReasonOr<Ok> jsop_checkaliasedlexical(EnvironmentCoordinate ec);
     AbortReasonOr<Ok> jsop_funcall(uint32_t argc);
     AbortReasonOr<Ok> jsop_funapply(uint32_t argc);
     AbortReasonOr<Ok> jsop_funapplyarguments(uint32_t argc);
     AbortReasonOr<Ok> jsop_funapplyarray(uint32_t argc);
-    AbortReasonOr<Ok> jsop_call(uint32_t argc, bool constructing);
+    AbortReasonOr<Ok> jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue);
     AbortReasonOr<Ok> jsop_eval(uint32_t argc);
     AbortReasonOr<Ok> jsop_label();
     AbortReasonOr<Ok> jsop_andor(JSOp op);
     AbortReasonOr<Ok> jsop_dup2();
     AbortReasonOr<Ok> jsop_loophead(jsbytecode* pc);
     AbortReasonOr<Ok> jsop_compare(JSOp op);
     AbortReasonOr<Ok> jsop_compare(JSOp op, MDefinition* left, MDefinition* right);
     AbortReasonOr<Ok> getStaticName(bool* emitted, JSObject* staticObject, PropertyName* name,
@@ -1172,34 +1172,40 @@ class IonBuilder
 
 class CallInfo
 {
     MDefinition* fun_;
     MDefinition* thisArg_;
     MDefinition* newTargetArg_;
     MDefinitionVector args_;
 
-    bool constructing_;
-    bool setter_;
+    bool constructing_:1;
+
+    // True if the caller does not use the return value.
+    bool ignoresReturnValue_:1;
+
+    bool setter_:1;
 
   public:
-    CallInfo(TempAllocator& alloc, bool constructing)
+    CallInfo(TempAllocator& alloc, bool constructing, bool ignoresReturnValue)
       : fun_(nullptr),
         thisArg_(nullptr),
         newTargetArg_(nullptr),
         args_(alloc),
         constructing_(constructing),
+        ignoresReturnValue_(ignoresReturnValue),
         setter_(false)
     { }
 
     MOZ_MUST_USE bool init(CallInfo& callInfo) {
         MOZ_ASSERT(constructing_ == callInfo.constructing());
 
         fun_ = callInfo.fun();
         thisArg_ = callInfo.thisArg();
+        ignoresReturnValue_ = callInfo.ignoresReturnValue();
 
         if (constructing())
             newTargetArg_ = callInfo.getNewTarget();
 
         if (!args_.appendAll(callInfo.argv()))
             return false;
 
         return true;
@@ -1286,16 +1292,20 @@ class CallInfo
     void setThis(MDefinition* thisArg) {
         thisArg_ = thisArg;
     }
 
     bool constructing() const {
         return constructing_;
     }
 
+    bool ignoresReturnValue() const {
+        return ignoresReturnValue_;
+    }
+
     void setNewTarget(MDefinition* newTarget) {
         MOZ_ASSERT(constructing());
         newTargetArg_ = newTarget;
     }
     MDefinition* getNewTarget() const {
         MOZ_ASSERT(newTargetArg_);
         return newTargetArg_;
     }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1971,26 +1971,26 @@ WrappedFunction::WrappedFunction(JSFunct
     isNative_(fun->isNative()),
     isConstructor_(fun->isConstructor()),
     isClassConstructor_(fun->isClassConstructor()),
     isSelfHostedBuiltin_(fun->isSelfHostedBuiltin())
 {}
 
 MCall*
 MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
-           bool construct, bool isDOMCall)
+           bool construct, bool ignoresReturnValue, bool isDOMCall)
 {
     WrappedFunction* wrappedTarget = target ? new(alloc) WrappedFunction(target) : nullptr;
     MOZ_ASSERT(maxArgc >= numActualArgs);
     MCall* ins;
     if (isDOMCall) {
         MOZ_ASSERT(!construct);
         ins = new(alloc) MCallDOMNative(wrappedTarget, numActualArgs);
     } else {
-        ins = new(alloc) MCall(wrappedTarget, numActualArgs, construct);
+        ins = new(alloc) MCall(wrappedTarget, numActualArgs, construct, ignoresReturnValue);
     }
     if (!ins->init(alloc, maxArgc + NumNonArgumentOperands))
         return nullptr;
     return ins;
 }
 
 AliasSet
 MCallDOMNative::getAliasSet() const
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4076,33 +4076,37 @@ class MCall
   protected:
     // Monomorphic cache of single target from TI, or nullptr.
     WrappedFunction* target_;
 
     // Original value of argc from the bytecode.
     uint32_t numActualArgs_;
 
     // True if the call is for JSOP_NEW.
-    bool construct_;
-
-    bool needsArgCheck_;
-
-    MCall(WrappedFunction* target, uint32_t numActualArgs, bool construct)
+    bool construct_:1;
+
+    // True if the caller does not use the return value.
+    bool ignoresReturnValue_:1;
+
+    bool needsArgCheck_:1;
+
+    MCall(WrappedFunction* target, uint32_t numActualArgs, bool construct, bool ignoresReturnValue)
       : target_(target),
         numActualArgs_(numActualArgs),
         construct_(construct),
+        ignoresReturnValue_(ignoresReturnValue),
         needsArgCheck_(true)
     {
         setResultType(MIRType::Value);
     }
 
   public:
     INSTRUCTION_HEADER(Call)
     static MCall* New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs,
-                      bool construct, bool isDOMCall);
+                      bool construct, bool ignoresReturnValue, bool isDOMCall);
 
     void initFunction(MDefinition* func) {
         initOperand(FunctionOperandIndex, func);
     }
 
     bool needsArgCheck() const {
         return needsArgCheck_;
     }
@@ -4137,16 +4141,20 @@ class MCall
     WrappedFunction* getSingleTarget() const {
         return target_;
     }
 
     bool isConstructing() const {
         return construct_;
     }
 
+    bool ignoresReturnValue() const {
+        return ignoresReturnValue_;
+    }
+
     // The number of stack arguments is the max between the number of formal
     // arguments and the number of actual arguments. The number of stack
     // argument includes the |undefined| padding added in case of underflow.
     // Includes |this|.
     uint32_t numStackArgs() const {
         return numOperands() - NumNonArgumentOperands;
     }
 
@@ -4180,32 +4188,33 @@ class MCall
 class MCallDOMNative : public MCall
 {
     // A helper class for MCalls for DOM natives.  Note that this is NOT
     // actually a separate MIR op from MCall, because all sorts of places use
     // isCall() to check for calls and all we really want is to overload a few
     // virtual things from MCall.
   protected:
     MCallDOMNative(WrappedFunction* target, uint32_t numActualArgs)
-        : MCall(target, numActualArgs, false)
+        : MCall(target, numActualArgs, false, false)
     {
         MOZ_ASSERT(getJitInfo()->type() != JSJitInfo::InlinableNative);
 
         // If our jitinfo is not marked eliminatable, that means that our C++
         // implementation is fallible or that it never wants to be eliminated or
         // that we have no hope of ever doing the sort of argument analysis that
         // would allow us to detemine that we're side-effect-free.  In the
         // latter case we wouldn't get DCEd no matter what, but for the former
         // two cases we have to explicitly say that we can't be DCEd.
         if (!getJitInfo()->isEliminatable)
             setGuard();
     }
 
     friend MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc,
-                             size_t numActualArgs, bool construct, bool isDOMCall);
+                             size_t numActualArgs, bool construct, bool ignoresReturnValue,
+                             bool isDOMCall);
 
     const JSJitInfo* getJitInfo() const;
   public:
     virtual AliasSet getAliasSet() const override;
 
     virtual bool congruentTo(const MDefinition* ins) const override;
 
     virtual bool isCallDOMNative() const override {
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -49,18 +49,18 @@ AutoDetectInvalidation::AutoDetectInvali
 void
 VMFunction::addToFunctions()
 {
     this->next = functions;
     functions = this;
 }
 
 bool
-InvokeFunction(JSContext* cx, HandleObject obj, bool constructing, uint32_t argc, Value* argv,
-               MutableHandleValue rval)
+InvokeFunction(JSContext* cx, HandleObject obj, bool constructing, bool ignoresReturnValue,
+               uint32_t argc, Value* argv, MutableHandleValue rval)
 {
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogStartEvent(logger, TraceLogger_Call);
 
     AutoArrayRooter argvRoot(cx, argc + 1 + constructing, argv);
 
     // Data in the argument vector is arranged for a JIT -> JIT call.
     RootedValue thisv(cx, argv[0]);
@@ -99,33 +99,33 @@ InvokeFunction(JSContext* cx, HandleObje
 
         // Otherwise the default |this| has already been created.  We could
         // almost perform a *call* at this point, but we'd break |new.target|
         // in the function.  So in this one weird case we call a one-off
         // construction path that *won't* set |this| to JS_IS_CONSTRUCTING.
         return InternalConstructWithProvidedThis(cx, fval, thisv, cargs, newTarget, rval);
     }
 
-    InvokeArgs args(cx);
+    InvokeArgsMaybeIgnoresReturnValue args(cx, ignoresReturnValue);
     if (!args.init(cx, argc))
         return false;
 
     for (size_t i = 0; i < argc; i++)
         args[i].set(argvWithoutThis[i]);
 
     return Call(cx, fval, thisv, args, rval);
 }
 
 bool
 InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs,
                                uint32_t numFormalArgs, Value* argv, MutableHandleValue rval)
 {
     MOZ_ASSERT(numFormalArgs > numActualArgs);
     argv[1 + numActualArgs] = argv[1 + numFormalArgs];
-    return InvokeFunction(cx, obj, true, numActualArgs, argv, rval);
+    return InvokeFunction(cx, obj, true, false, numActualArgs, argv, rval);
 }
 
 #ifdef JS_SIMULATOR
 static bool
 CheckSimulatorRecursionLimitWithExtra(JSContext* cx, uint32_t extra)
 {
     if (cx->simulator()->overRecursedWithExtra(extra)) {
         ReportOverRecursed(cx);
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -597,18 +597,18 @@ class AutoDetectInvalidation
 
     ~AutoDetectInvalidation() {
         if (!disabled_ && ionScript_->invalidated())
             setReturnOverride();
     }
 };
 
 MOZ_MUST_USE bool
-InvokeFunction(JSContext* cx, HandleObject obj0, bool constructing, uint32_t argc, Value* argv,
-               MutableHandleValue rval);
+InvokeFunction(JSContext* cx, HandleObject obj0, bool constructing, bool ignoresReturnValue,
+               uint32_t argc, Value* argv, MutableHandleValue rval);
 MOZ_MUST_USE bool
 InvokeFunctionShuffleNewTarget(JSContext* cx, HandleObject obj, uint32_t numActualArgs,
                                uint32_t numFormalArgs, Value* argv, MutableHandleValue rval);
 
 bool CheckOverRecursed(JSContext* cx);
 bool CheckOverRecursedWithExtra(JSContext* cx, BaselineFrame* frame,
                                 uint32_t extra, uint32_t earlyCheck);
 
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1905,16 +1905,19 @@ class LJSCallInstructionHelper : public 
     // Does not include |this|.
     uint32_t numActualArgs() const {
         return mir()->numActualArgs();
     }
 
     bool isConstructing() const {
         return mir()->isConstructing();
     }
+    bool ignoresReturnValue() const {
+        return mir()->ignoresReturnValue();
+    }
 };
 
 // Generates a polymorphic callsite, wherein the function being called is
 // unknown and anticipated to vary.
 class LCallGeneric : public LJSCallInstructionHelper<BOX_PIECES, 1, 2>
 {
   public:
     LIR_HEADER(CallGeneric)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2307,16 +2307,17 @@ typedef bool
  */
 struct JSJitInfo {
     enum OpType {
         Getter,
         Setter,
         Method,
         StaticMethod,
         InlinableNative,
+        IgnoresReturnValueNative,
         // Must be last
         OpTypeCount
     };
 
     enum ArgType {
         // Basic types
         String = (1 << 0),
         Integer = (1 << 1), // Only 32-bit or less
@@ -2398,18 +2399,23 @@ struct JSJitInfo {
     }
 
     union {
         JSJitGetterOp getter;
         JSJitSetterOp setter;
         JSJitMethodOp method;
         /** A DOM static method, used for Promise wrappers */
         JSNative staticMethod;
+        JSNative ignoresReturnValueMethod;
     };
 
+    static unsigned offsetOfIgnoresReturnValueNative() {
+        return offsetof(JSJitInfo, ignoresReturnValueMethod);
+    }
+
     union {
         uint16_t protoID;
         js::jit::InlinableNative inlinableNative;
     };
 
     union {
         uint16_t depth;
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -567,16 +567,20 @@ class JSFunction : public js::NativeObje
                       "for offsetOfNativeOrScript() have any sense");
         static_assert(offsetof(U, n.native) == offsetof(U, nativeOrScript),
                       "U::nativeOrScript must be at the same offset as "
                       "native");
 
         return offsetof(JSFunction, u.nativeOrScript);
     }
 
+    static unsigned offsetOfJitInfo() {
+        return offsetof(JSFunction, u.n.jitinfo);
+    }
+
     inline void trace(JSTracer* trc);
 
     /* Bound function accessors. */
 
     JSObject* getBoundFunctionTarget() const;
     const js::Value& getBoundFunctionThis() const;
     const js::Value& getBoundFunctionArgument(unsigned which) const;
     size_t getBoundFunctionArgumentCount() const;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -128,17 +128,18 @@ js::StackUses(JSScript* script, jsbyteco
     switch (op) {
       case JSOP_POPN:
         return GET_UINT16(pc);
       case JSOP_NEW:
       case JSOP_SUPERCALL:
         return 2 + GET_ARGC(pc) + 1;
       default:
         /* stack: fun, this, [argc arguments] */
-        MOZ_ASSERT(op == JSOP_CALL || op == JSOP_EVAL || op == JSOP_CALLITER ||
+        MOZ_ASSERT(op == JSOP_CALL || op == JSOP_CALL_IGNORES_RV || op == JSOP_EVAL ||
+                   op == JSOP_CALLITER ||
                    op == JSOP_STRICTEVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
         return 2 + GET_ARGC(pc);
     }
 }
 
 unsigned
 js::StackDefs(JSScript* script, jsbytecode* pc)
 {
@@ -1868,16 +1869,17 @@ ExpressionDecompiler::decompilePC(jsbyte
         return write(js_undefined_str);
       case JSOP_GLOBALTHIS:
         // |this| could convert to a very long object initialiser, so cite it by
         // its keyword name.
         return write(js_this_str);
       case JSOP_NEWTARGET:
         return write("new.target");
       case JSOP_CALL:
+      case JSOP_CALL_IGNORES_RV:
       case JSOP_CALLITER:
       case JSOP_FUNCALL:
       case JSOP_FUNAPPLY:
         return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
                write("(...)");
       case JSOP_SPREADCALL:
         return decompilePCForStackOperand(pc, -3) &&
                write("(...)");
@@ -2389,17 +2391,17 @@ DecompileArgumentFromStack(JSContext* cx
 
     MOZ_ASSERT(script->containsPC(current));
 
     if (current < script->main())
         return true;
 
     /* Don't handle getters, setters or calls from fun.call/fun.apply. */
     JSOp op = JSOp(*current);
-    if (op != JSOP_CALL && op != JSOP_NEW)
+    if (op != JSOP_CALL && op != JSOP_CALL_IGNORES_RV && op != JSOP_NEW)
         return true;
 
     if (static_cast<unsigned>(formalIndex) >= GET_ARGC(current))
         return true;
 
     BytecodeParser parser(cx, script);
     if (!parser.parse())
         return false;
@@ -2452,16 +2454,18 @@ js::CallResultEscapes(jsbytecode* pc)
      *
      * If we see any of these sequences, the result is only tested for nullness:
      * - call / ifeq
      * - call / not / ifeq
      */
 
     if (*pc == JSOP_CALL)
         pc += JSOP_CALL_LENGTH;
+    else if (*pc == JSOP_CALL_IGNORES_RV)
+        pc += JSOP_CALL_IGNORES_RV_LENGTH;
     else if (*pc == JSOP_SPREADCALL)
         pc += JSOP_SPREADCALL_LENGTH;
     else
         return true;
 
     if (*pc == JSOP_POP)
         return false;
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -440,17 +440,23 @@ js::InternalCallOrConstruct(JSContext* c
     RootedFunction fun(cx, &args.callee().as<JSFunction>());
     if (construct != CONSTRUCT && fun->isClassConstructor()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
         return false;
     }
 
     if (fun->isNative()) {
         MOZ_ASSERT_IF(construct, !fun->isConstructor());
-        return CallJSNative(cx, fun->native(), args);
+        JSNative native = fun->native();
+        if (!construct && args.ignoresReturnValue()) {
+            const JSJitInfo* jitInfo = fun->jitInfo();
+            if (jitInfo && jitInfo->type() == JSJitInfo::IgnoresReturnValueNative)
+                native = jitInfo->ignoresReturnValueMethod;
+        }
+        return CallJSNative(cx, native, args);
     }
 
     if (!JSFunction::getOrCreateScript(cx, fun))
         return false;
 
     /* Run function until JSOP_RETRVAL, JSOP_RETURN or error. */
     InvokeState state(cx, args, construct);
 
@@ -2917,28 +2923,30 @@ CASE(JSOP_FUNAPPLY)
     CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp);
     if (!GuardFunApplyArgumentsOptimization(cx, REGS.fp(), args))
         goto error;
     /* FALL THROUGH */
 }
 
 CASE(JSOP_NEW)
 CASE(JSOP_CALL)
+CASE(JSOP_CALL_IGNORES_RV)
 CASE(JSOP_CALLITER)
 CASE(JSOP_SUPERCALL)
 CASE(JSOP_FUNCALL)
 {
     if (REGS.fp()->hasPushedGeckoProfilerFrame())
         cx->runtime()->geckoProfiler().updatePC(script, REGS.pc);
 
     MaybeConstruct construct = MaybeConstruct(*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
+    bool ignoresReturnValue = *REGS.pc == JSOP_CALL_IGNORES_RV;
     unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
 
     MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
-    CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct);
+    CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct, ignoresReturnValue);
 
     JSFunction* maybeFun;
     bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
 
     /* Don't bother trying to fast-path calls to scripted non-constructors. */
     if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor() ||
         (!construct && maybeFun->isClassConstructor()))
     {
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -2292,24 +2292,33 @@ 1234567890123456789012345678901234567890
      * This opcode is a no-op and it indicates the location of a jump
      * instruction target. Some other opcodes act as jump targets, such as
      * LOOPENTRY, as well as all which are matched by BytecodeIsJumpTarget
      * function.
      *   Category: Other
      *   Operands:
      *   Stack: =>
      */ \
-    macro(JSOP_JUMPTARGET,  230, "jumptarget",     NULL,  1,  0,  0,  JOF_BYTE)
+    macro(JSOP_JUMPTARGET,  230, "jumptarget",     NULL,  1,  0,  0,  JOF_BYTE)\
+    /*
+     * Like JSOP_CALL, but tells the function that the return value is ignored.
+     * stack.
+     *   Category: Statements
+     *   Type: Function
+     *   Operands: uint16_t argc
+     *   Stack: callee, this, args[0], ..., args[argc-1] => rval
+     *   nuses: (argc+2)
+     */ \
+    macro(JSOP_CALL_IGNORES_RV, 231, "call-ignores-rv", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
 
 /*
  * In certain circumstances it may be useful to "pad out" the opcode space to
  * a power of two.  Use this macro to do so.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
-    macro(231) \
     macro(232) \
     macro(233) \
     macro(234) \
     macro(235) \
     macro(236) \
     macro(237) \
     macro(238) \
     macro(239) \
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1040,16 +1040,27 @@ class FixedArgsBase
 class InvokeArgs : public detail::GenericArgsBase<NO_CONSTRUCT>
 {
     using Base = detail::GenericArgsBase<NO_CONSTRUCT>;
 
   public:
     explicit InvokeArgs(JSContext* cx) : Base(cx) {}
 };
 
+/** Function call args of statically-unknown count. */
+class InvokeArgsMaybeIgnoresReturnValue : public detail::GenericArgsBase<NO_CONSTRUCT>
+{
+    using Base = detail::GenericArgsBase<NO_CONSTRUCT>;
+
+  public:
+    explicit InvokeArgsMaybeIgnoresReturnValue(JSContext* cx, bool ignoresReturnValue) : Base(cx) {
+        this->ignoresReturnValue_ = ignoresReturnValue;
+    }
+};
+
 /** Function call args of statically-known count. */
 template <size_t N>
 class FixedInvokeArgs : public detail::FixedArgsBase<NO_CONSTRUCT, N>
 {
     using Base = detail::FixedArgsBase<NO_CONSTRUCT, N>;
 
   public:
     explicit FixedInvokeArgs(JSContext* cx) : Base(cx) {}