Bug 989204 part 2 - Don't use bound functions for arrow functions. r=jorendorff,bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 03 Apr 2014 11:29:04 +0200
changeset 176952 9517fe15e2c20adc58a200394f8ad6aa8945fdf5
parent 176951 8cef7a6e9f7980dfc08a0e8d678e741b9f6c1669
child 176953 7cdf043c7666583837a64d82e768bf85b8ac6fe1
push id26538
push userryanvm@gmail.com
push dateThu, 03 Apr 2014 19:23:59 +0000
treeherdermozilla-central@6c924a018540 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bhackett
bugs989204
milestone31.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 989204 part 2 - Don't use bound functions for arrow functions. r=jorendorff,bhackett
js/src/frontend/Parser.cpp
js/src/jit-test/tests/arrow-functions/length.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonMacroAssembler.cpp
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jsfun.cpp
js/src/jsfun.h
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Xdr.h
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1252,18 +1252,21 @@ Parser<ParseHandler>::newFunction(Generi
         pc = pc->parent;
 
     RootedFunction fun(context);
     JSFunction::Flags flags = (kind == Expression)
                               ? JSFunction::INTERPRETED_LAMBDA
                               : (kind == Arrow)
                                 ? JSFunction::INTERPRETED_LAMBDA_ARROW
                                 : JSFunction::INTERPRETED;
+    gc::AllocKind allocKind = (kind == Arrow)
+                              ? JSFunction::ExtendedFinalizeKind
+                              : JSFunction::FinalizeKind;
     fun = NewFunctionWithProto(context, NullPtr(), nullptr, 0, flags, NullPtr(), atom, proto,
-                               JSFunction::FinalizeKind, MaybeSingletonObject);
+                               allocKind, MaybeSingletonObject);
     if (!fun)
         return nullptr;
     if (options().selfHostingMode)
         fun->setIsSelfHostedBuiltin();
     return fun;
 }
 
 static bool
--- a/js/src/jit-test/tests/arrow-functions/length.js
+++ b/js/src/jit-test/tests/arrow-functions/length.js
@@ -2,10 +2,10 @@
 
 assertEq((a => a).hasOwnProperty("length"), true);
 
 assertEq((a => a).length, 1);
 assertEq((() => 0).length, 0);
 assertEq(((a) => 0).length, 1);
 assertEq(((a, b) => 0).length, 2);
 
-assertEq(((...arr) => arr).length, 1);
-assertEq(((a=1, b=2) => 0).length, 2);
+assertEq(((...arr) => arr).length, 0);
+assertEq(((a=1, b=2) => 0).length, 0);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1098,16 +1098,27 @@ BaselineCompiler::emit_JSOP_NULL()
 {
     frame.push(NullValue());
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_THIS()
 {
+    if (function() && function()->isArrow()) {
+        // Arrow functions store their (lexical) |this| value in an
+        // extended slot.
+        frame.syncStack(0);
+        Register scratch = R0.scratchReg();
+        masm.loadPtr(frame.addressOfCallee(), scratch);
+        masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowThisSlot()), R0);
+        frame.push(R0);
+        return true;
+    }
+
     // Keep this value in R0
     frame.pushThis();
 
     // In strict mode code or self-hosted functions, |this| is left alone.
     if (script->strict() || (function() && function()->isSelfHostedBuiltin()))
         return true;
 
     Label skipIC;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4057,16 +4057,25 @@ CodeGenerator::visitComputeThis(LCompute
     masm.branchTestObject(Assembler::NotEqual, value, ool->entry());
     masm.unboxObject(value, output);
 
     masm.bind(ool->rejoin());
     return true;
 }
 
 bool
+CodeGenerator::visitLoadArrowThis(LLoadArrowThis *lir)
+{
+    Register callee = ToRegister(lir->callee());
+    ValueOperand output = ToOutValue(lir);
+    masm.loadValue(Address(callee, FunctionExtended::offsetOfArrowThisSlot()), output);
+    return true;
+}
+
+bool
 CodeGenerator::visitArrayLength(LArrayLength *lir)
 {
     Address length(ToRegister(lir->elements()), ObjectElements::offsetOfLength());
     masm.load32(length, ToRegister(lir->output()));
     return true;
 }
 
 bool
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -155,16 +155,17 @@ class CodeGenerator : public CodeGenerat
     bool visitCreateThis(LCreateThis *lir);
     bool visitCreateThisWithProto(LCreateThisWithProto *lir);
     bool visitCreateThisWithTemplate(LCreateThisWithTemplate *lir);
     bool visitCreateArgumentsObject(LCreateArgumentsObject *lir);
     bool visitGetArgumentsObjectArg(LGetArgumentsObjectArg *lir);
     bool visitSetArgumentsObjectArg(LSetArgumentsObjectArg *lir);
     bool visitReturnFromCtor(LReturnFromCtor *lir);
     bool visitComputeThis(LComputeThis *lir);
+    bool visitLoadArrowThis(LLoadArrowThis *lir);
     bool visitArrayLength(LArrayLength *lir);
     bool visitSetArrayLength(LSetArrayLength *lir);
     bool visitTypedArrayLength(LTypedArrayLength *lir);
     bool visitTypedArrayElements(LTypedArrayElements *lir);
     bool visitNeuterCheck(LNeuterCheck *lir);
     bool visitTypedObjectElements(LTypedObjectElements *lir);
     bool visitStringLength(LStringLength *lir);
     bool visitInitializedLength(LInitializedLength *lir);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1652,28 +1652,20 @@ IonBuilder::inspectOpcode(JSOp op)
         return jsop_length();
 
       case JSOP_NOT:
         return jsop_not();
 
       case JSOP_THIS:
         return jsop_this();
 
-      case JSOP_CALLEE:
-      {
-        MDefinition *callee;
-        if (inliningDepth_ == 0) {
-            MInstruction *calleeIns = MCallee::New(alloc());
-            current->add(calleeIns);
-            callee = calleeIns;
-        } else {
-            callee = inlineCallInfo_->fun();
-        }
-        current->push(callee);
-        return true;
+      case JSOP_CALLEE: {
+         MDefinition *callee = getCallee();
+         current->push(callee);
+         return true;
       }
 
       case JSOP_GETPROP:
       case JSOP_CALLPROP:
       {
         PropertyName *name = info().getAtom(pc)->asPropertyName();
         return jsop_getprop(name);
       }
@@ -9529,16 +9521,24 @@ IonBuilder::jsop_deffun(uint32_t index)
 }
 
 bool
 IonBuilder::jsop_this()
 {
     if (!info().funMaybeLazy())
         return abort("JSOP_THIS outside of a JSFunction.");
 
+    if (info().funMaybeLazy()->isArrow()) {
+        // Arrow functions store their lexical |this| in an extended slot.
+        MLoadArrowThis *thisObj = MLoadArrowThis::New(alloc(), getCallee());
+        current->add(thisObj);
+        current->push(thisObj);
+        return true;
+    }
+
     if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) {
         // No need to wrap primitive |this| in strict mode or self-hosted code.
         current->pushSlot(info().thisSlot());
         return true;
     }
 
     if (thisTypes->getKnownTypeTag() == JSVAL_TYPE_OBJECT ||
         (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject()))
@@ -10225,8 +10225,20 @@ IonBuilder::constant(const Value &v)
     return c;
 }
 
 MConstant *
 IonBuilder::constantInt(int32_t i)
 {
     return constant(Int32Value(i));
 }
+
+MDefinition *
+IonBuilder::getCallee()
+{
+    if (inliningDepth_ == 0) {
+        MInstruction *callee = MCallee::New(alloc());
+        current->add(callee);
+        return callee;
+    }
+
+    return inlineCallInfo_->fun();
+}
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -529,16 +529,18 @@ class IonBuilder : public MIRGenerator
                                             TypeDescrSet objTypeReprs,
                                             TypeDescrSet elemTypeReprs,
                                             int32_t elemSize);
 
     // Typed array helpers.
     MInstruction *getTypedArrayLength(MDefinition *obj);
     MInstruction *getTypedArrayElements(MDefinition *obj);
 
+    MDefinition *getCallee();
+
     bool jsop_add(MDefinition *left, MDefinition *right);
     bool jsop_bitnot();
     bool jsop_bitop(JSOp op);
     bool jsop_binary(JSOp op);
     bool jsop_binary(JSOp op, MDefinition *left, MDefinition *right);
     bool jsop_pos();
     bool jsop_neg();
     bool jsop_setarg(uint32_t arg);
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -1841,34 +1841,33 @@ MacroAssembler::branchIfNotInterpretedCo
     // perform an aligned 32-bit load and adjust the bitmask accordingly.
     JS_ASSERT(JSFunction::offsetOfNargs() % sizeof(uint32_t) == 0);
     JS_ASSERT(JSFunction::offsetOfFlags() == JSFunction::offsetOfNargs() + 2);
     JS_STATIC_ASSERT(IS_LITTLE_ENDIAN);
 
     // Emit code for the following test:
     //
     // bool isInterpretedConstructor() const {
-    //     return isInterpreted() && !isFunctionPrototype() &&
+    //     return isInterpreted() && !isFunctionPrototype() && !isArrow() &&
     //         (!isSelfHostedBuiltin() || isSelfHostedConstructor());
     // }
 
     // First, ensure it's a scripted function.
     load32(Address(fun, JSFunction::offsetOfNargs()), scratch);
     branchTest32(Assembler::Zero, scratch, Imm32(JSFunction::INTERPRETED << 16), label);
 
-    // Common case: if both IS_FUN_PROTO and SELF_HOSTED are not set,
-    // we're done.
+    // Common case: if IS_FUN_PROTO, ARROW and SELF_HOSTED are not set,
+    // the function is an interpreted constructor and we're done.
     Label done;
-    uint32_t bits = (JSFunction::IS_FUN_PROTO | JSFunction::SELF_HOSTED) << 16;
+    uint32_t bits = (JSFunction::IS_FUN_PROTO | JSFunction::ARROW | JSFunction::SELF_HOSTED) << 16;
     branchTest32(Assembler::Zero, scratch, Imm32(bits), &done);
     {
-        // The callee is either Function.prototype or self-hosted. Fail if
-        // SELF_HOSTED_CTOR is not set. This means the callee must be
-        // Function.prototype or a self-hosted function that's not a
-        // constructor.
+        // The callee is either Function.prototype, an arrow function or
+        // self-hosted. None of these are constructible, except self-hosted
+        // constructors, so branch to |label| if SELF_HOSTED_CTOR is not set.
         branchTest32(Assembler::Zero, scratch, Imm32(JSFunction::SELF_HOSTED_CTOR << 16), label);
 
 #ifdef DEBUG
         // Function.prototype should not have the SELF_HOSTED_CTOR flag.
         branchTest32(Assembler::Zero, scratch, Imm32(JSFunction::IS_FUN_PROTO << 16), &done);
         breakpoint();
 #endif
     }
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -1051,16 +1051,30 @@ class LComputeThis : public LInstruction
         return getDef(0);
     }
 
     MComputeThis *mir() const {
         return mir_->toComputeThis();
     }
 };
 
+class LLoadArrowThis : public LInstructionHelper<BOX_PIECES, 1, 0>
+{
+  public:
+    LLoadArrowThis(const LAllocation &callee) {
+        setOperand(0, callee);
+    }
+
+    LIR_HEADER(LoadArrowThis)
+
+    const LAllocation *callee() {
+        return getOperand(0);
+    }
+};
+
 // Writes a typed argument for a function call to the frame's argument vector.
 class LStackArgT : public LInstructionHelper<0, 1, 0>
 {
     uint32_t argslot_; // Index into frame-scope argument vector.
     MIRType type_;
 
   public:
     LIR_HEADER(StackArgT)
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -59,16 +59,17 @@
     _(CreateThis)                   \
     _(CreateThisWithProto)          \
     _(CreateThisWithTemplate)       \
     _(CreateArgumentsObject)        \
     _(GetArgumentsObjectArg)        \
     _(SetArgumentsObjectArg)        \
     _(ReturnFromCtor)               \
     _(ComputeThis)                  \
+    _(LoadArrowThis)                \
     _(BitNotI)                      \
     _(BitNotV)                      \
     _(BitOpI)                       \
     _(BitOpV)                       \
     _(ShiftI)                       \
     _(UrshD)                        \
     _(Return)                       \
     _(Throw)                        \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -410,16 +410,26 @@ LIRGenerator::visitComputeThis(MComputeT
     // they aren't clobbered.
     if (!useBox(lir, LComputeThis::ValueIndex, ins->input()))
         return false;
 
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
+LIRGenerator::visitLoadArrowThis(MLoadArrowThis *ins)
+{
+    JS_ASSERT(ins->type() == MIRType_Value);
+    JS_ASSERT(ins->callee()->type() == MIRType_Object);
+
+    LLoadArrowThis *lir = new(alloc()) LLoadArrowThis(useRegister(ins->callee()));
+    return defineBox(lir, ins);
+}
+
+bool
 LIRGenerator::lowerCallArguments(MCall *call)
 {
     uint32_t argc = call->numStackArgs();
     if (argc > maxargslots_)
         maxargslots_ = argc;
 
     for (size_t i = 0; i < argc; i++) {
         MDefinition *arg = call->getArg(i);
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -90,16 +90,17 @@ class LIRGenerator : public LIRGenerator
     bool visitCreateThisWithTemplate(MCreateThisWithTemplate *ins);
     bool visitCreateThisWithProto(MCreateThisWithProto *ins);
     bool visitCreateThis(MCreateThis *ins);
     bool visitCreateArgumentsObject(MCreateArgumentsObject *ins);
     bool visitGetArgumentsObjectArg(MGetArgumentsObjectArg *ins);
     bool visitSetArgumentsObjectArg(MSetArgumentsObjectArg *ins);
     bool visitReturnFromCtor(MReturnFromCtor *ins);
     bool visitComputeThis(MComputeThis *ins);
+    bool visitLoadArrowThis(MLoadArrowThis *ins);
     bool visitCall(MCall *call);
     bool visitApplyArgs(MApplyArgs *apply);
     bool visitBail(MBail *bail);
     bool visitAssertFloat32(MAssertFloat32 *ins);
     bool visitGetDynamicName(MGetDynamicName *ins);
     bool visitFilterArgumentsOrEval(MFilterArgumentsOrEval *ins);
     bool visitCallDirectEval(MCallDirectEval *ins);
     bool visitTest(MTest *test);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4393,16 +4393,49 @@ class MComputeThis
     bool possiblyCalls() const {
         return true;
     }
 
     // Note: don't override getAliasSet: the thisObject hook can be
     // effectful.
 };
 
+// Load an arrow function's |this| value.
+class MLoadArrowThis
+  : public MUnaryInstruction,
+    public SingleObjectPolicy
+{
+    MLoadArrowThis(MDefinition *callee)
+      : MUnaryInstruction(callee)
+    {
+        setResultType(MIRType_Value);
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(LoadArrowThis)
+
+    static MLoadArrowThis *New(TempAllocator &alloc, MDefinition *callee) {
+        return new(alloc) MLoadArrowThis(callee);
+    }
+    MDefinition *callee() const {
+        return getOperand(0);
+    }
+    TypePolicy *typePolicy() {
+        return this;
+    }
+    bool congruentTo(MDefinition *ins) const {
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const {
+        // An arrow function's lexical |this| value is immutable.
+        return AliasSet::None();
+    }
+};
+
 class MPhi MOZ_FINAL : public MDefinition, public InlineForwardListNode<MPhi>
 {
     js::Vector<MUse, 2, IonAllocPolicy> inputs_;
 
     uint32_t slot_;
     bool hasBackedgeType_;
     bool triedToSpecialize_;
     bool isIterator_;
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -33,16 +33,17 @@ namespace jit {
     _(DefFun)                                                               \
     _(CreateThis)                                                           \
     _(CreateThisWithProto)                                                  \
     _(CreateThisWithTemplate)                                               \
     _(CreateArgumentsObject)                                                \
     _(GetArgumentsObjectArg)                                                \
     _(SetArgumentsObjectArg)                                                \
     _(ComputeThis)                                                          \
+    _(LoadArrowThis)                                                        \
     _(Call)                                                                 \
     _(ApplyArgs)                                                            \
     _(Bail)                                                                 \
     _(AssertFloat32)                                                        \
     _(GetDynamicName)                                                       \
     _(FilterArgumentsOrEval)                                                \
     _(CallDirectEval)                                                       \
     _(BitNot)                                                               \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -131,16 +131,17 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(DefFun)
     UNSAFE_OP(CreateThis)
     CUSTOM_OP(CreateThisWithTemplate)
     UNSAFE_OP(CreateThisWithProto)
     UNSAFE_OP(CreateArgumentsObject)
     UNSAFE_OP(GetArgumentsObjectArg)
     UNSAFE_OP(SetArgumentsObjectArg)
     UNSAFE_OP(ComputeThis)
+    UNSAFE_OP(LoadArrowThis)
     CUSTOM_OP(Call)
     UNSAFE_OP(ApplyArgs)
     UNSAFE_OP(Bail)
     UNSAFE_OP(AssertFloat32)
     UNSAFE_OP(GetDynamicName)
     UNSAFE_OP(FilterArgumentsOrEval)
     UNSAFE_OP(CallDirectEval)
     SAFE_OP(BitNot)
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -160,17 +160,17 @@ static const uint16_t poisonPillProps[] 
 static bool
 fun_enumerate(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->is<JSFunction>());
 
     RootedId id(cx);
     bool found;
 
-    if (!obj->isBoundFunction()) {
+    if (!obj->isBoundFunction() && !obj->as<JSFunction>().isArrow()) {
         id = NameToId(cx->names().prototype);
         if (!JSObject::hasProperty(cx, obj, id, &found, 0))
             return false;
     }
 
     id = NameToId(cx->names().length);
     if (!JSObject::hasProperty(cx, obj, id, &found, 0))
         return false;
@@ -281,18 +281,20 @@ js::fun_resolve(JSContext *cx, HandleObj
          * created eagerly.
          *
          * ES5 15.3.4: the non-native function object named Function.prototype
          * does not have a .prototype property.
          *
          * ES5 15.3.4.5: bound functions don't have a prototype property. The
          * isBuiltin() test covers this case because bound functions are native
          * (and thus built-in) functions by definition/construction.
+         *
+         * ES6 19.2.4.3: arrow functions also don't have a prototype property.
          */
-        if (fun->isBuiltin() || fun->isFunctionPrototype())
+        if (fun->isBuiltin() || fun->isArrow() || fun->isFunctionPrototype())
             return true;
 
         if (!ResolveInterpretedFunctionPrototype(cx, fun))
             return false;
         objp.set(fun);
         return true;
     }
 
@@ -415,38 +417,40 @@ js::XDRInterpretedFunction(XDRState<mode
         JS_ASSERT_IF(fun->hasSingletonType() &&
                      !((lazy && lazy->hasBeenCloned()) || (script && script->hasBeenCloned())),
                      fun->environment() == nullptr);
     }
 
     if (!xdr->codeUint32(&firstword))
         return false;
 
+    if ((firstword & HasAtom) && !XDRAtom(xdr, &atom))
+        return false;
+    if (!xdr->codeUint32(&flagsword))
+        return false;
+
     if (mode == XDR_DECODE) {
         JSObject *proto = nullptr;
         if (firstword & IsStarGenerator) {
             proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
             if (!proto)
                 return false;
         }
 
+        gc::AllocKind allocKind = uint16_t(flagsword) & JSFunction::EXTENDED
+                                  ? JSFunction::ExtendedFinalizeKind
+                                  : JSFunction::FinalizeKind;
         fun = NewFunctionWithProto(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED,
                                    /* parent = */ NullPtr(), NullPtr(), proto,
-                                   JSFunction::FinalizeKind, TenuredObject);
+                                   allocKind, TenuredObject);
         if (!fun)
             return false;
-        atom = nullptr;
         script = nullptr;
     }
 
-    if ((firstword & HasAtom) && !XDRAtom(xdr, &atom))
-        return false;
-    if (!xdr->codeUint32(&flagsword))
-        return false;
-
     if (firstword & IsLazy) {
         if (!XDRLazyScript(xdr, enclosingScope, enclosingScript, fun, &lazy))
             return false;
     } else {
         if (!XDRScript(xdr, enclosingScope, enclosingScript, fun, &script))
             return false;
     }
 
@@ -482,20 +486,23 @@ js::CloneFunctionAndScript(JSContext *cx
 {
     /* NB: Keep this in sync with XDRInterpretedFunction. */
     JSObject *cloneProto = nullptr;
     if (srcFun->isStarGenerator()) {
         cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global());
         if (!cloneProto)
             return nullptr;
     }
+
+    gc::AllocKind allocKind = srcFun->isExtended()
+                              ? JSFunction::ExtendedFinalizeKind
+                              : JSFunction::FinalizeKind;
     RootedFunction clone(cx, NewFunctionWithProto(cx, NullPtr(), nullptr, 0,
                                                   JSFunction::INTERPRETED, NullPtr(), NullPtr(),
-                                                  cloneProto, JSFunction::FinalizeKind,
-                                                  TenuredObject));
+                                                  cloneProto, allocKind, TenuredObject));
     if (!clone)
         return nullptr;
 
     RootedScript srcScript(cx, srcFun->getOrCreateScript(cx));
     if (!srcScript)
         return nullptr;
     RootedScript clonedScript(cx, CloneScript(cx, enclosingScope, clone, srcScript));
     if (!clonedScript)
@@ -677,25 +684,16 @@ FindBody(JSContext *cx, HandleFunction f
 }
 
 JSString *
 js::FunctionToString(JSContext *cx, HandleFunction fun, bool bodyOnly, bool lambdaParen)
 {
     if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx))
         return nullptr;
 
-    // If the object is an automatically-bound arrow function, get the source
-    // of the pre-binding target.
-    if (fun->isArrow() && fun->isBoundFunction()) {
-        JSObject *target = fun->getBoundFunctionTarget();
-        RootedFunction targetFun(cx, &target->as<JSFunction>());
-        JS_ASSERT(targetFun->isArrow());
-        return FunctionToString(cx, targetFun, bodyOnly, lambdaParen);
-    }
-
     if (IsAsmJSModule(fun))
         return AsmJSModuleToString(cx, fun, !lambdaParen);
     if (IsAsmJSFunction(fun))
         return AsmJSFunctionToString(cx, fun);
 
     StringBuffer out(cx);
     RootedScript script(cx);
 
@@ -1243,27 +1241,16 @@ JSFunction::relazify(JSTracer *trc)
 /* ES5 15.3.4.5.1 and 15.3.4.5.2. */
 bool
 js::CallOrConstructBoundFunction(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedFunction fun(cx, &args.callee().as<JSFunction>());
     JS_ASSERT(fun->isBoundFunction());
 
-    bool constructing = args.isConstructing();
-    if (constructing && fun->isArrow()) {
-        /*
-         * Per spec, arrow functions do not even have a [[Construct]] method.
-         * So before anything else, if we are an arrow function, make sure we
-         * don't even get here. You never saw me. Burn this comment.
-         */
-        RootedValue v(cx, ObjectValue(*fun));
-        return ReportIsNotFunction(cx, v, -1, CONSTRUCT);
-    }
-
     /* 15.3.4.5.1 step 1, 15.3.4.5.2 step 3. */
     unsigned argslen = fun->getBoundFunctionArgumentCount();
 
     if (args.length() + argslen > ARGS_LENGTH_MAX) {
         js_ReportAllocationOverflow(cx);
         return false;
     }
 
@@ -1280,16 +1267,17 @@ js::CallOrConstructBoundFunction(JSConte
     /* 15.3.4.5.1, 15.3.4.5.2 step 4. */
     for (unsigned i = 0; i < argslen; i++)
         invokeArgs[i].set(fun->getBoundFunctionArgument(i));
     PodCopy(invokeArgs.array() + argslen, vp + 2, args.length());
 
     /* 15.3.4.5.1, 15.3.4.5.2 step 5. */
     invokeArgs.setCallee(ObjectValue(*target));
 
+    bool constructing = args.isConstructing();
     if (!constructing)
         invokeArgs.setThis(boundThis);
 
     if (constructing ? !InvokeConstructor(cx, invokeArgs) : !Invoke(cx, invokeArgs))
         return false;
 
     args.rval().set(invokeArgs.rval());
     return true;
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -137,37 +137,27 @@ class JSFunction : public JSObject
 
     bool hasJITCode() const {
         if (!hasScript())
             return false;
 
         return nonLazyScript()->hasBaselineScript() || nonLazyScript()->hasIonScript();
     }
 
-    // Arrow functions are a little weird.
-    //
-    // Like all functions, (1) when the compiler parses an arrow function, it
-    // creates a function object that gets stored with the enclosing script;
-    // and (2) at run time the script's function object is cloned.
-    //
-    // But then, unlike other functions, (3) a bound function is created with
-    // the clone as its target.
-    //
-    // isArrow() is true for all three of these Function objects.
-    // isBoundFunction() is true only for the last one.
+    // Arrow functions store their lexical |this| in the first extended slot.
     bool isArrow()                  const { return flags() & ARROW; }
 
     /* Compound attributes: */
     bool isBuiltin() const {
         return isNative() || isSelfHostedBuiltin();
     }
     bool isInterpretedConstructor() const {
         // Note: the JITs inline this check, so be careful when making changes
         // here. See IonMacroAssembler::branchIfNotInterpretedConstructor.
-        return isInterpreted() && !isFunctionPrototype() &&
+        return isInterpreted() && !isFunctionPrototype() && !isArrow() &&
                (!isSelfHostedBuiltin() || isSelfHostedConstructor());
     }
     bool isNamedLambda() const {
         return isLambda() && displayAtom() && !hasGuessedAtom();
     }
     bool hasParallelNative() const {
         return isNative() && jitInfo() && jitInfo()->hasParallelNative();
     }
@@ -543,16 +533,20 @@ bool IsConstructor(const Value &v);
  * Most functions do not have these extensions, but enough do that efficient
  * storage is required (no malloc'ed reserved slots).
  */
 class FunctionExtended : public JSFunction
 {
   public:
     static const unsigned NUM_EXTENDED_SLOTS = 2;
 
+    static inline size_t offsetOfArrowThisSlot() {
+        return offsetof(FunctionExtended, extendedSlots) + 0 * sizeof(extendedSlots[0]);
+    }
+
   private:
     friend class JSFunction;
 
     /* Reserved slots available for storage by particular native functions. */
     HeapValue extendedSlots[NUM_EXTENDED_SLOTS];
 };
 
 extern JSFunction *
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -26,16 +26,26 @@
 #include "vm/String-inl.h"
 
 namespace js {
 
 inline bool
 ComputeThis(JSContext *cx, AbstractFramePtr frame)
 {
     JS_ASSERT_IF(frame.isInterpreterFrame(), !frame.asInterpreterFrame()->runningInJit());
+
+    if (frame.isFunctionFrame() && frame.fun()->isArrow()) {
+        /*
+         * Arrow functions store their (lexical) |this| value in an
+         * extended slot.
+         */
+        frame.thisValue() = frame.fun()->getExtendedSlot(0);
+        return true;
+    }
+
     if (frame.thisValue().isObject())
         return true;
     RootedValue thisv(cx, frame.thisValue());
     if (frame.isFunctionFrame()) {
         if (frame.fun()->strict() || frame.fun()->isSelfHostedBuiltin())
             return true;
         /*
          * Eval function frames have their own |this| slot, which is a copy of the function's
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3622,20 +3622,18 @@ JSObject *
 js::LambdaArrow(JSContext *cx, HandleFunction fun, HandleObject parent, HandleValue thisv)
 {
     MOZ_ASSERT(fun->isArrow());
 
     RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent, TenuredObject));
     if (!clone)
         return nullptr;
 
-    clone = js_fun_bind(cx, clone, thisv, nullptr, 0);
-    if (!clone)
-        return nullptr;
-    clone->as<JSFunction>().setArrow();
+    MOZ_ASSERT(clone->as<JSFunction>().isArrow());
+    clone->as<JSFunction>().setExtendedSlot(0, thisv);
 
     MOZ_ASSERT(clone->global() == clone->global());
     return clone;
 }
 
 bool
 js::DefFunOperation(JSContext *cx, HandleScript script, HandleObject scopeChain,
                     HandleFunction funArg)
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -18,17 +18,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 170);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 171);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;