Bug 1405943 - Part 1: Implement Pipeline Operator |>. r=arai
authorHiroaki Karasawa <hkarasawa@g.ecc.u-tokyo.ac.jp>
Tue, 17 Oct 2017 00:10:00 -0400
changeset 386671 7371550c7fc5
parent 386670 2dca3fd72002
child 386672 0e5d50e17fef
push id32699
push userarchaeopteryx@coole-files.de
push date2017-10-17 21:52 +0000
treeherdermozilla-central@f78d59473334 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1405943
milestone58.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 1405943 - Part 1: Implement Pipeline Operator |>. r=arai
js/moz.configure
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/TokenKind.h
js/src/frontend/TokenStream.cpp
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -249,8 +249,20 @@ with only_when('--enable-compile-environ
         if fuzzing and not afl:
             return True
 
     set_config('FUZZING', enable_fuzzing)
     set_define('FUZZING', enable_fuzzing)
 
     set_config('LIBFUZZER', enable_libfuzzer)
     set_define('LIBFUZZER', enable_libfuzzer)
+
+# Enable pipeline operator
+# ===================================================
+js_option('--enable-pipeline-operator', default=False, help='Enable pipeline operator')
+
+@depends('--enable-pipeline-operator')
+def enable_pipeline_operator(value):
+    if value:
+        return True
+
+set_config('ENABLE_PIPELINE_OPERATOR', enable_pipeline_operator)
+set_define('ENABLE_PIPELINE_OPERATOR', enable_pipeline_operator)
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -73,17 +73,17 @@ enum BinaryOperator {
     BINOP_LT, BINOP_LE, BINOP_GT, BINOP_GE,
     /* shift */
     BINOP_LSH, BINOP_RSH, BINOP_URSH,
     /* arithmetic */
     BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, BINOP_POW,
     /* binary */
     BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND,
     /* misc */
-    BINOP_IN, BINOP_INSTANCEOF,
+    BINOP_IN, BINOP_INSTANCEOF, BINOP_PIPELINE,
 
     BINOP_LIMIT
 };
 
 enum UnaryOperator {
     UNOP_ERR = -1,
 
     UNOP_DELETE = 0,
@@ -149,16 +149,17 @@ static const char* const binopNames[] = 
     "/",          /* BINOP_DIV */
     "%",          /* BINOP_MOD */
     "**",         /* BINOP_POW */
     "|",          /* BINOP_BITOR */
     "^",          /* BINOP_BITXOR */
     "&",          /* BINOP_BITAND */
     "in",         /* BINOP_IN */
     "instanceof", /* BINOP_INSTANCEOF */
+    "|>",         /* BINOP_PIPELINE */
 };
 
 static const char* const unopNames[] = {
     "delete",  /* UNOP_DELETE */
     "-",       /* UNOP_NEG */
     "+",       /* UNOP_POS */
     "!",       /* UNOP_NOT */
     "~",       /* UNOP_BITNOT */
@@ -1947,16 +1948,18 @@ ASTSerializer::binop(ParseNodeKind kind)
       case PNK_BITXOR:
         return BINOP_BITXOR;
       case PNK_BITAND:
         return BINOP_BITAND;
       case PNK_IN:
         return BINOP_IN;
       case PNK_INSTANCEOF:
         return BINOP_INSTANCEOF;
+      case PNK_PIPELINE:
+        return BINOP_PIPELINE;
       default:
         return BINOP_ERR;
     }
 }
 
 bool
 ASTSerializer::statements(ParseNode* pn, NodeVector& elts)
 {
@@ -2890,16 +2893,17 @@ ASTSerializer::expression(ParseNode* pn,
         LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT);
 
         RootedValue lhs(cx), rhs(cx);
         return pattern(pn->pn_left, &lhs) &&
                expression(pn->pn_right, &rhs) &&
                builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst);
       }
 
+      case PNK_PIPELINE:
       case PNK_ADD:
       case PNK_SUB:
       case PNK_STRICTEQ:
       case PNK_EQ:
       case PNK_STRICTNE:
       case PNK_NE:
       case PNK_LT:
       case PNK_LE:
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3382,16 +3382,22 @@ BytecodeEmitter::checkSideEffects(ParseN
       case PNK_NEW:
       case PNK_CALL:
       case PNK_TAGGED_TEMPLATE:
       case PNK_SUPERCALL:
         MOZ_ASSERT(pn->isArity(PN_LIST));
         *answer = true;
         return true;
 
+      case PNK_PIPELINE:
+        MOZ_ASSERT(pn->isArity(PN_LIST));
+        MOZ_ASSERT(pn->pn_count >= 2);
+        *answer = true;
+        return true;
+
       // Classes typically introduce names.  Even if no name is introduced,
       // the heritage and/or class body (through computed property names)
       // usually have effects.
       case PNK_CLASS:
         MOZ_ASSERT(pn->isArity(PN_TERNARY));
         *answer = true;
         return true;
 
@@ -9462,16 +9468,123 @@ BytecodeEmitter::isRestParameter(ParseNo
             return paramName && name == paramName;
         }
     }
 
     return false;
 }
 
 bool
+BytecodeEmitter::emitCallee(ParseNode* callee, ParseNode* call, bool spread, bool* callop)
+{
+    switch (callee->getKind()) {
+      case PNK_NAME:
+        if (!emitGetName(callee, *callop))
+            return false;
+        break;
+      case PNK_DOT:
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+        if (callee->as<PropertyAccess>().isSuper()) {
+            if (!emitSuperPropOp(callee, JSOP_GETPROP_SUPER, /* isCall = */ *callop))
+                return false;
+        } else {
+            if (!emitPropOp(callee, *callop ? JSOP_CALLPROP : JSOP_GETPROP))
+                return false;
+        }
+
+        break;
+      case PNK_ELEM:
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
+        if (callee->as<PropertyByValue>().isSuper()) {
+            if (!emitSuperElemOp(callee, JSOP_GETELEM_SUPER, /* isCall = */ *callop))
+                return false;
+        } else {
+            if (!emitElemOp(callee, *callop ? JSOP_CALLELEM : JSOP_GETELEM))
+                return false;
+            if (*callop) {
+                if (!emit1(JSOP_SWAP))
+                    return false;
+            }
+        }
+
+        break;
+      case PNK_FUNCTION:
+        /*
+         * Top level lambdas which are immediately invoked should be
+         * treated as only running once. Every time they execute we will
+         * create new types and scripts for their contents, to increase
+         * the quality of type information within them and enable more
+         * backend optimizations. Note that this does not depend on the
+         * lambda being invoked at most once (it may be named or be
+         * accessed via foo.caller indirection), as multiple executions
+         * will just cause the inner scripts to be repeatedly cloned.
+         */
+        MOZ_ASSERT(!emittingRunOnceLambda);
+        if (checkRunOnceContext()) {
+            emittingRunOnceLambda = true;
+            if (!emitTree(callee))
+                return false;
+            emittingRunOnceLambda = false;
+        } else {
+            if (!emitTree(callee))
+                return false;
+        }
+        *callop = false;
+        break;
+      case PNK_SUPERBASE:
+        MOZ_ASSERT(call->isKind(PNK_SUPERCALL));
+        MOZ_ASSERT(parser.isSuperBase(callee));
+        if (!emit1(JSOP_SUPERFUN))
+            return false;
+        break;
+      default:
+        if (!emitTree(callee))
+            return false;
+        *callop = false;             /* trigger JSOP_UNDEFINED after */
+        break;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitPipeline(ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isArity(PN_LIST));
+    MOZ_ASSERT(pn->pn_count >= 2);
+
+    if (!emitTree(pn->pn_head))
+        return false;
+
+    ParseNode* callee = pn->pn_head->pn_next;
+
+    do {
+        bool callop = true;
+        if (!emitCallee(callee, pn, false, &callop))
+            return false;
+
+        // Emit room for |this|
+        if (!callop) {
+            if (!emit1(JSOP_UNDEFINED))
+                return false;
+        }
+
+        if (!emit2(JSOP_PICK, 2))
+            return false;
+
+        if (!emitCall(JSOP_CALL, 1, pn))
+            return false;
+
+        checkTypeSet(JSOP_CALL);
+    } while ((callee = callee->pn_next));
+
+    return true;
+}
+
+bool
 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.
      *
@@ -9489,104 +9602,42 @@ BytecodeEmitter::emitCallOrNew(ParseNode
 
     if (argc >= ARGC_LIMIT) {
         parser.reportError(callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
         return false;
     }
 
     ParseNode* pn2 = pn->pn_head;
     bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
-    switch (pn2->getKind()) {
-      case PNK_NAME:
-        if (emitterMode == BytecodeEmitter::SelfHosting && !spread) {
-            // Calls to "forceInterpreter", "callFunction",
-            // "callContentFunction", or "resumeGenerator" in self-hosted
-            // code generate inline bytecode.
-            if (pn2->name() == cx->names().callFunction ||
-                pn2->name() == cx->names().callContentFunction ||
-                pn2->name() == cx->names().constructContentFunction)
-            {
-                return emitSelfHostedCallFunction(pn);
-            }
-            if (pn2->name() == cx->names().resumeGenerator)
-                return emitSelfHostedResumeGenerator(pn);
-            if (pn2->name() == cx->names().forceInterpreter)
-                return emitSelfHostedForceInterpreter(pn);
-            if (pn2->name() == cx->names().allowContentIter)
-                return emitSelfHostedAllowContentIter(pn);
-            if (pn2->name() == cx->names().defineDataPropertyIntrinsic && pn->pn_count == 4)
-                return emitSelfHostedDefineDataProperty(pn);
-            if (pn2->name() == cx->names().hasOwn)
-                return emitSelfHostedHasOwn(pn);
-            // Fall through.
-        }
-        if (!emitGetName(pn2, callop))
-            return false;
-        break;
-      case PNK_DOT:
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (pn2->as<PropertyAccess>().isSuper()) {
-            if (!emitSuperPropOp(pn2, JSOP_GETPROP_SUPER, /* isCall = */ callop))
-                return false;
-        } else {
-            if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP))
-                return false;
-        }
-
-        break;
-      case PNK_ELEM:
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (pn2->as<PropertyByValue>().isSuper()) {
-            if (!emitSuperElemOp(pn2, JSOP_GETELEM_SUPER, /* isCall = */ callop))
-                return false;
-        } else {
-            if (!emitElemOp(pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM))
-                return false;
-            if (callop) {
-                if (!emit1(JSOP_SWAP))
-                    return false;
-            }
-        }
-
-        break;
-      case PNK_FUNCTION:
-        /*
-         * Top level lambdas which are immediately invoked should be
-         * treated as only running once. Every time they execute we will
-         * create new types and scripts for their contents, to increase
-         * the quality of type information within them and enable more
-         * backend optimizations. Note that this does not depend on the
-         * lambda being invoked at most once (it may be named or be
-         * accessed via foo.caller indirection), as multiple executions
-         * will just cause the inner scripts to be repeatedly cloned.
-         */
-        MOZ_ASSERT(!emittingRunOnceLambda);
-        if (checkRunOnceContext()) {
-            emittingRunOnceLambda = true;
-            if (!emitTree(pn2))
-                return false;
-            emittingRunOnceLambda = false;
-        } else {
-            if (!emitTree(pn2))
-                return false;
-        }
-        callop = false;
-        break;
-      case PNK_SUPERBASE:
-        MOZ_ASSERT(pn->isKind(PNK_SUPERCALL));
-        MOZ_ASSERT(parser.isSuperBase(pn2));
-        if (!emit1(JSOP_SUPERFUN))
-            return false;
-        break;
-      default:
-        if (!emitTree(pn2))
-            return false;
-        callop = false;             /* trigger JSOP_UNDEFINED after */
-        break;
-    }
+
+    if (pn2->isKind(PNK_NAME) && emitterMode == BytecodeEmitter::SelfHosting && !spread) {
+        // Calls to "forceInterpreter", "callFunction",
+        // "callContentFunction", or "resumeGenerator" in self-hosted
+        // code generate inline bytecode.
+        if (pn2->name() == cx->names().callFunction ||
+            pn2->name() == cx->names().callContentFunction ||
+            pn2->name() == cx->names().constructContentFunction)
+        {
+            return emitSelfHostedCallFunction(pn);
+        }
+        if (pn2->name() == cx->names().resumeGenerator)
+            return emitSelfHostedResumeGenerator(pn);
+        if (pn2->name() == cx->names().forceInterpreter)
+            return emitSelfHostedForceInterpreter(pn);
+        if (pn2->name() == cx->names().allowContentIter)
+            return emitSelfHostedAllowContentIter(pn);
+        if (pn2->name() == cx->names().defineDataPropertyIntrinsic && pn->pn_count == 4)
+            return emitSelfHostedDefineDataProperty(pn);
+        if (pn2->name() == cx->names().hasOwn)
+            return emitSelfHostedHasOwn(pn);
+        // Fall through
+    }
+
+    if (!emitCallee(pn2, pn, spread, &callop))
+        return false;
 
     bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW ||
                    pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;
 
 
     // Emit room for |this|.
     if (!callop) {
         if (isNewOp) {
@@ -9696,16 +9747,19 @@ BytecodeEmitter::emitCallOrNew(ParseNode
         if (!emitUint32Operand(JSOP_LINENO, lineNum))
             return false;
     }
 
     return true;
 }
 
 static const JSOp ParseNodeKindToJSOp[] = {
+    // JSOP_NOP is for pipeline operator which does not emit its own JSOp
+    // but has highest precedence in binary operators
+    JSOP_NOP,
     JSOP_OR,
     JSOP_AND,
     JSOP_BITOR,
     JSOP_BITXOR,
     JSOP_BITAND,
     JSOP_STRICTEQ,
     JSOP_EQ,
     JSOP_STRICTNE,
@@ -11082,16 +11136,21 @@ BytecodeEmitter::emitTree(ParseNode* pn,
             return false;
         break;
 
       case PNK_POW:
         if (!emitRightAssociative(pn))
             return false;
         break;
 
+      case PNK_PIPELINE:
+        if (!emitPipeline(pn))
+            return false;
+        break;
+
       case PNK_TYPEOFNAME:
         if (!emitTypeof(pn, JSOP_TYPEOF))
             return false;
         break;
 
       case PNK_TYPEOFEXPR:
         if (!emitTypeof(pn, JSOP_TYPEOFEXPR))
             return false;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -830,14 +830,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
 
     MOZ_MUST_USE bool emitClass(ParseNode* pn);
     MOZ_MUST_USE bool emitSuperPropLHS(ParseNode* superBase, bool isCall = false);
     MOZ_MUST_USE bool emitSuperPropOp(ParseNode* pn, JSOp op, bool isCall = false);
     MOZ_MUST_USE bool emitSuperElemOperands(ParseNode* pn,
                                             EmitElemOption opts = EmitElemOption::Get);
     MOZ_MUST_USE bool emitSuperElemOp(ParseNode* pn, JSOp op, bool isCall = false);
+
+    MOZ_MUST_USE bool emitCallee(ParseNode* callee, ParseNode* call, bool spread, bool* callop);
+
+    MOZ_MUST_USE bool emitPipeline(ParseNode* pn);
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_BytecodeEmitter_h */
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -400,16 +400,21 @@ ContainsHoistedDeclaration(JSContext* cx
       case PNK_NEWTARGET:
       case PNK_POSHOLDER:
       case PNK_SUPERCALL:
       case PNK_SUPERBASE:
       case PNK_SETTHIS:
         MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
                   "some parent node without recurring to test this node");
 
+      case PNK_PIPELINE:
+        MOZ_ASSERT(node->isArity(PN_LIST));
+        *result = false;
+        return true;
+
       case PNK_LIMIT: // invalid sentinel value
         MOZ_CRASH("unexpected PNK_LIMIT in node");
     }
 
     MOZ_CRASH("invalid node kind");
 }
 
 /*
@@ -1719,16 +1724,19 @@ Fold(JSContext* cx, ParseNode** pnp, Par
 
       case PNK_SEMI:
       case PNK_THIS:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         if (ParseNode*& expr = pn->pn_kid)
             return Fold(cx, &expr, parser, inGenexpLambda);
         return true;
 
+      case PNK_PIPELINE:
+        return true;
+
       case PNK_AND:
       case PNK_OR:
         return FoldAndOr(cx, pnp, parser, inGenexpLambda);
 
       case PNK_FUNCTION:
         return FoldFunction(cx, pn, parser, inGenexpLambda);
 
       case PNK_MODULE:
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -682,16 +682,17 @@ class NameResolver
           case PNK_RSH:
           case PNK_URSH:
           case PNK_ADD:
           case PNK_SUB:
           case PNK_STAR:
           case PNK_DIV:
           case PNK_MOD:
           case PNK_POW:
+          case PNK_PIPELINE:
           case PNK_COMMA:
           case PNK_NEW:
           case PNK_CALL:
           case PNK_SUPERCALL:
           case PNK_GENEXP:
           case PNK_ARRAY:
           case PNK_STATEMENTLIST:
           case PNK_PARAMSBODY:
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -442,16 +442,17 @@ PushNodeChildren(ParseNode* pn, NodeStac
       case PNK_RSH:
       case PNK_URSH:
       case PNK_ADD:
       case PNK_SUB:
       case PNK_STAR:
       case PNK_DIV:
       case PNK_MOD:
       case PNK_POW:
+      case PNK_PIPELINE:
       case PNK_COMMA:
       case PNK_NEW:
       case PNK_CALL:
       case PNK_SUPERCALL:
       case PNK_GENEXP:
       case PNK_ARRAY:
       case PNK_OBJECT:
       case PNK_TEMPLATE_STRING_LIST:
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -124,16 +124,17 @@ class ObjectBox;
     F(NOT) \
     F(BITNOT) \
     F(AWAIT) \
     \
     /* \
      * Binary operators. \
      * These must be in the same order as TOK_OR and friends in TokenStream.h. \
      */ \
+    F(PIPELINE) \
     F(OR) \
     F(AND) \
     F(BITOR) \
     F(BITXOR) \
     F(BITAND) \
     F(STRICTEQ) \
     F(EQ) \
     F(STRICTNE) \
@@ -181,17 +182,17 @@ class ObjectBox;
  * The long comment after this enum block describes the kinds in detail.
  */
 enum ParseNodeKind : uint16_t
 {
 #define EMIT_ENUM(name) PNK_##name,
     FOR_EACH_PARSE_NODE_KIND(EMIT_ENUM)
 #undef EMIT_ENUM
     PNK_LIMIT, /* domain size */
-    PNK_BINOP_FIRST = PNK_OR,
+    PNK_BINOP_FIRST = PNK_PIPELINE,
     PNK_BINOP_LAST = PNK_POW,
     PNK_ASSIGNMENT_START = PNK_ASSIGN,
     PNK_ASSIGNMENT_LAST = PNK_POWASSIGN
 };
 
 inline bool
 IsDeleteKind(ParseNodeKind kind)
 {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -7992,43 +7992,44 @@ Parser<ParseHandler, CharT>::expr(InHand
 static ParseNodeKind
 BinaryOpTokenKindToParseNodeKind(TokenKind tok)
 {
     MOZ_ASSERT(TokenKindIsBinaryOp(tok));
     return ParseNodeKind(PNK_BINOP_FIRST + (tok - TOK_BINOP_FIRST));
 }
 
 static const int PrecedenceTable[] = {
-    1, /* PNK_OR */
-    2, /* PNK_AND */
-    3, /* PNK_BITOR */
-    4, /* PNK_BITXOR */
-    5, /* PNK_BITAND */
-    6, /* PNK_STRICTEQ */
-    6, /* PNK_EQ */
-    6, /* PNK_STRICTNE */
-    6, /* PNK_NE */
-    7, /* PNK_LT */
-    7, /* PNK_LE */
-    7, /* PNK_GT */
-    7, /* PNK_GE */
-    7, /* PNK_INSTANCEOF */
-    7, /* PNK_IN */
-    8, /* PNK_LSH */
-    8, /* PNK_RSH */
-    8, /* PNK_URSH */
-    9, /* PNK_ADD */
-    9, /* PNK_SUB */
-    10, /* PNK_STAR */
-    10, /* PNK_DIV */
-    10, /* PNK_MOD */
-    11  /* PNK_POW */
+    1, /* PNK_PIPELINE */
+    2, /* PNK_OR */
+    3, /* PNK_AND */
+    4, /* PNK_BITOR */
+    5, /* PNK_BITXOR */
+    6, /* PNK_BITAND */
+    7, /* PNK_STRICTEQ */
+    7, /* PNK_EQ */
+    7, /* PNK_STRICTNE */
+    7, /* PNK_NE */
+    8, /* PNK_LT */
+    8, /* PNK_LE */
+    8, /* PNK_GT */
+    8, /* PNK_GE */
+    8, /* PNK_INSTANCEOF */
+    8, /* PNK_IN */
+    9, /* PNK_LSH */
+    9, /* PNK_RSH */
+    9, /* PNK_URSH */
+    10, /* PNK_ADD */
+    10, /* PNK_SUB */
+    11, /* PNK_STAR */
+    11, /* PNK_DIV */
+    11, /* PNK_MOD */
+    12  /* PNK_POW */
 };
 
-static const int PRECEDENCE_CLASSES = 11;
+static const int PRECEDENCE_CLASSES = 12;
 
 static int
 Precedence(ParseNodeKind pnk) {
     // Everything binds tighter than PNK_LIMIT, because we want to reduce all
     // nodes to a single node when we reach a token that is not another binary
     // operator.
     if (pnk == PNK_LIMIT)
         return 0;
--- a/js/src/frontend/TokenKind.h
+++ b/js/src/frontend/TokenKind.h
@@ -150,18 +150,19 @@
     /* \
      * The following token types occupy contiguous ranges to enable easy \
      * range-testing. \
      */ \
     /* \
      * Binary operators tokens, TOK_OR thru TOK_POW. These must be in the same \
      * order as F(OR) and friends in FOR_EACH_PARSE_NODE_KIND in ParseNode.h. \
      */ \
+    macro(PIPELINE,     "'|>'") \
+    range(BINOP_FIRST,  PIPELINE) \
     macro(OR,           "'||'")   /* logical or */ \
-    range(BINOP_FIRST, OR) \
     macro(AND,          "'&&'")   /* logical and */ \
     macro(BITOR,        "'|'")    /* bitwise-or */ \
     macro(BITXOR,       "'^'")    /* bitwise-xor */ \
     macro(BITAND,       "'&'")    /* bitwise-and */ \
     \
     /* Equality operation tokens, per TokenKindIsEquality. */ \
     macro(STRICTEQ,     "'==='") \
     range(EQUALITY_START, STRICTEQ) \
@@ -191,17 +192,17 @@
     range(SHIFTOP_LAST, URSH) \
     \
     macro(ADD,          "'+'") \
     macro(SUB,          "'-'") \
     macro(MUL,          "'*'") \
     macro(DIV,          "'/'") \
     macro(MOD,          "'%'") \
     macro(POW,          "'**'") \
-    range(BINOP_LAST, POW) \
+    range(BINOP_LAST,   POW) \
     \
     /* Unary operation tokens. */ \
     macro(TYPEOF,       "keyword 'typeof'") \
     range(KEYWORD_UNOP_FIRST, TYPEOF) \
     macro(VOID,         "keyword 'void'") \
     range(KEYWORD_UNOP_LAST, VOID) \
     macro(NOT,          "'!'") \
     macro(BITNOT,       "'~'") \
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1725,16 +1725,20 @@ TokenStream::getTokenInternal(TokenKind*
             goto identifier;
         }
         goto badchar;
       }
 
       case '|':
         if (matchChar('|'))
             tp->type = TOK_OR;
+#ifdef ENABLE_PIPELINE_OPERATOR
+        else if (matchChar('>'))
+            tp->type = TOK_PIPELINE;
+#endif
         else
             tp->type = matchChar('=') ? TOK_BITORASSIGN : TOK_BITOR;
         goto out;
 
       case '^':
         tp->type = matchChar('=') ? TOK_BITXORASSIGN : TOK_BITXOR;
         goto out;