Skip to content

Commit

Permalink
Implement exponentiation operator.
Browse files Browse the repository at this point in the history
Summary:
Add the `**` operator to Hermes.

- Add the tokens to the lexer
- Update the parser to allow for right-associative binary operators
  - Break the stack popping loop early to build from the right with **
  - Error when using a UnaryOperator before ** (must use parens)
- Update the IR to parse the corresponding strings
- Add the HermesInternal call to perform the operation

Reviewed By: tmikov

Differential Revision: D16628557

fbshipit-source-id: 1d94cd2c7f13ccf694415f7cd28b3061bf5eb38c
  • Loading branch information
avp authored and facebook-github-bot committed Aug 28, 2019
1 parent c0d849e commit ae0bc93
Show file tree
Hide file tree
Showing 24 changed files with 372 additions and 13 deletions.
4 changes: 2 additions & 2 deletions include/hermes/BCGen/HBC/BytecodeFileFormat.h
Expand Up @@ -28,8 +28,8 @@ const static uint64_t MAGIC = 0x1F1903C103BC1FC6;
const static uint64_t DELTA_MAGIC = ~MAGIC;

// Bytecode version generated by this version of the compiler.
// Updated: Aug 1, 2019
const static uint32_t BYTECODE_VERSION = 61;
// Updated: Aug 28, 2019
const static uint32_t BYTECODE_VERSION = 62;

/// Property cache index which indicates no caching.
static constexpr uint8_t PROPERTY_CACHING_DISABLED = 0;
Expand Down
19 changes: 19 additions & 0 deletions include/hermes/BCGen/Lowering.h
Expand Up @@ -121,6 +121,25 @@ class LowerCondBranch : public FunctionPass {
static bool isOperatorSupported(BinaryOperatorInst::OpKind op);
};

/// Iterates over all instructions and performs lowering on exponentiation
/// operators to turn them into HermesInternal calls.
/// NOTE: It may be possible in the future to extend this pass to allow for
/// other lowering operations on single instructions.
class LowerExponentiationOperator : public FunctionPass {
public:
explicit LowerExponentiationOperator()
: FunctionPass("LowerExponentiationOperator") {}
~LowerExponentiationOperator() override = default;
bool runOnFunction(Function *F) override;

private:
/// Changes the binary exponentiation operator \p inst into a call to
/// HermesInternal.exponentiationOperator.
static bool lowerExponentiationOperator(
IRBuilder &builder,
BinaryOperatorInst *inst);
};

} // namespace hermes

#endif
1 change: 1 addition & 0 deletions include/hermes/IR/Instrs.h
Expand Up @@ -1599,6 +1599,7 @@ class BinaryOperatorInst : public Instruction {
OrKind, // | (|=)
XorKind, // ^ (^=)
AndKind, // & (^=)
ExponentiationKind, // ** (**=)
InKind, // "in"
InstanceOfKind, // instanceof
LAST_OPCODE
Expand Down
1 change: 1 addition & 0 deletions include/hermes/Inst/Builtins.def
Expand Up @@ -43,6 +43,7 @@ BUILTIN_METHOD(HermesInternal, ensureObject)
BUILTIN_METHOD(HermesInternal, copyDataProperties)
BUILTIN_METHOD(HermesInternal, copyRestArgs)
BUILTIN_METHOD(HermesInternal, exportAll)
BUILTIN_METHOD(HermesInternal, exponentiationOperator)

BUILTIN_OBJECT(JSON)
BUILTIN_METHOD(JSON, parse)
Expand Down
2 changes: 2 additions & 0 deletions include/hermes/Optimizer/PassManager/Pass.h
Expand Up @@ -15,6 +15,8 @@ using llvm::StringRef;

class Function;
class Module;
class Instruction;
class IRBuilder;

/// This class represents a pass, which is a transformation of the IR. Passes
/// are either Function passes, which transform one function, and are not
Expand Down
2 changes: 2 additions & 0 deletions include/hermes/Parser/TokenKinds.def
Expand Up @@ -96,6 +96,7 @@ PUNCTUATOR(comma, ",")
PUNCTUATOR(plusplus, "++")
PUNCTUATOR(minusminus, "--")
RANGE_MARKER(_first_binary)
BINOP( starstar, "**", 11)
BINOP( star, "*", 10)
BINOP( percent, "%", 10)
BINOP( slash, "/", 10)
Expand Down Expand Up @@ -126,6 +127,7 @@ PUNCTUATOR(equal, "=")
PUNCTUATOR(plusequal, "+=")
PUNCTUATOR(minusequal, "-=")
PUNCTUATOR(starequal, "*=")
PUNCTUATOR(starstarequal, "**=")
PUNCTUATOR(percentequal, "%=")
PUNCTUATOR(slashequal, "/=")
PUNCTUATOR(lesslessequal, "<<=")
Expand Down
34 changes: 34 additions & 0 deletions include/hermes/Support/Math.h
@@ -0,0 +1,34 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
#ifndef HERMES_SUPPORT_MATH_H
#define HERMES_SUPPORT_MATH_H

#include <cmath>

namespace hermes {

/// ES9.0 12.6.4
/// Perform the exponentiation operation on doubles required by the JS spec.
inline double expOp(double x, double y) {
constexpr double nan = std::numeric_limits<double>::quiet_NaN();

// Handle special cases that std::pow handles differently.
if (std::isnan(y)) {
return nan;
} else if (y == 0) {
return 1;
} else if (std::abs(x) == 1 && std::isinf(y)) {
return nan;
}

// std::pow handles the other edge cases as the ES9.0 spec requires.
return std::pow(x, y);
}

} // namespace hermes

#endif
1 change: 1 addition & 0 deletions include/hermes/VM/PredefinedStrings.def
Expand Up @@ -391,6 +391,7 @@ STR(ensureObject, "ensureObject")
STR(copyDataProperties, "copyDataProperties")
STR(copyRestArgs, "copyRestArgs")
STR(exportAll, "exportAll")
STR(exponentiationOperator, "exponentiationOperator")

STR(require, "require")
STR(requireFast, "requireFast")
Expand Down
5 changes: 4 additions & 1 deletion lib/BCGen/HBC/HBC.cpp
Expand Up @@ -53,7 +53,10 @@ void lowerIR(Module *M, const BytecodeGenerationOptions &options) {
// OptEnvironmentInit needs to run before LowerConstants.
PM.addPass(new OptEnvironmentInit());
}
/// LowerBuiltinCalls needs to run before the rest of the lowering.
// LowerExponentiationOperator needs to run before LowerBuiltinCalls because
// it introduces calls to HermesInternal.
PM.addPass(new LowerExponentiationOperator());
// LowerBuiltinCalls needs to run before the rest of the lowering.
PM.addPass(new LowerBuiltinCalls());
// It is important to run LowerNumericProperties before LoadConstants
// as LowerNumericProperties could generate new constants.
Expand Down
3 changes: 3 additions & 0 deletions lib/BCGen/HBC/ISel.cpp
Expand Up @@ -518,6 +518,9 @@ void HBCISel::generateBinaryOperatorInst(
BCFGen_->emitDiv(res, left, right);
}
break;
case OpKind::ExponentiationKind: // ** (**=)
llvm_unreachable("ExponentiationKind emits a HermesInternal call");
break;
case OpKind::ModuloKind: // % (%=)
BCFGen_->emitMod(res, left, right);
break;
Expand Down
43 changes: 43 additions & 0 deletions lib/BCGen/Lowering.cpp
Expand Up @@ -678,3 +678,46 @@ bool LowerCondBranch::runOnFunction(Function *F) {
}
return changed;
}

bool LowerExponentiationOperator::runOnFunction(Function *F) {
IRBuilder builder{F};
llvm::DenseSet<Instruction *> toTransform{};
bool changed = false;

for (BasicBlock &bb : *F) {
for (auto it = bb.begin(), e = bb.end(); it != e; /* empty */) {
auto *inst = &*it;
// Increment iterator before potentially erasing inst and invalidating
// iteration.
++it;
if (auto *binOp = dyn_cast<BinaryOperatorInst>(inst)) {
if (binOp->getOperatorKind() ==
BinaryOperatorInst::OpKind::ExponentiationKind) {
changed |= lowerExponentiationOperator(builder, binOp);
}
}
}
}

return changed;
}

bool LowerExponentiationOperator::lowerExponentiationOperator(
IRBuilder &builder,
BinaryOperatorInst *binOp) {
assert(
binOp->getOperatorKind() ==
BinaryOperatorInst::OpKind::ExponentiationKind &&
"lowerExponentiationOperator must take a ** operator");
// Replace a ** b with HermesInternal.exponentiationOperator(a, b)
builder.setInsertionPoint(binOp);
auto *result = builder.createCallInst(
builder.createLoadPropertyInst(
builder.createTryLoadGlobalPropertyInst("HermesInternal"),
"exponentiationOperator"),
builder.getLiteralUndefined(),
{binOp->getLeftHandSide(), binOp->getRightHandSide()});
binOp->replaceAllUsesWith(result);
binOp->eraseFromParent();
return true;
}
7 changes: 7 additions & 0 deletions lib/IR/IREval.cpp
Expand Up @@ -6,6 +6,7 @@
*/
#include "hermes/IR/IREval.h"
#include "hermes/IR/IRBuilder.h"
#include "hermes/Support/Math.h"

#include "llvm/ADT/SmallString.h"

Expand Down Expand Up @@ -548,6 +549,12 @@ Literal *hermes::evalBinaryOperator(
std::fmod(leftLiteralNum->getValue(), rightLiteralNum->getValue()));
}

break;
case OpKind::ExponentiationKind: // ** (**=)
if (leftLiteralNum && rightLiteralNum) {
return builder.getLiteralNumber(hermes::expOp(
leftLiteralNum->getValue(), rightLiteralNum->getValue()));
}
break;
case OpKind::OrKind: // | (|=)
if (leftLiteralNum && rightLiteralNum) {
Expand Down
9 changes: 5 additions & 4 deletions lib/IR/Instrs.cpp
Expand Up @@ -55,12 +55,13 @@ const char *UnaryOperatorInst::opStringRepr[] =
{"delete", "void", "typeof", "+", "-", "~", "!"};

const char *BinaryOperatorInst::opStringRepr[] = {
"", "==", "!=", "===", "!==", "<", "<=", ">", ">=", "<<", ">>",
">>>", "+", "-", "*", "/", "%", "|", "^", "&", "in", "instanceof"};
"", "==", "!=", "===", "!==", "<", "<=", ">",
">=", "<<", ">>", ">>>", "+", "-", "*", "/",
"%", "|", "^", "&", "**", "in", "instanceof"};

const char *BinaryOperatorInst::assignmentOpStringRepr[] = {
"=", "", "", "", "", "", "", "", "", "<<=", ">>=",
">>>=", "+=", "-=", "*=", "/=", "%=", "|=", "^=", "&=", "", ""};
"=", "", "", "", "", "", "", "", "", "<<=", ">>=", ">>>=",
"+=", "-=", "*=", "/=", "%=", "|=", "^=", "&=", "**=", "", ""};

UnaryOperatorInst::OpKind UnaryOperatorInst::parseOperator(StringRef op) {
for (int i = 0; i < static_cast<int>(BinaryOperatorInst::OpKind::LAST_OPCODE);
Expand Down
19 changes: 18 additions & 1 deletion lib/Parser/JSLexer.cpp
Expand Up @@ -207,11 +207,28 @@ const Token *JSLexer::advance(GrammarContext grammarContext) {
PUNC_L2_3('&', TokenKind::amp, '&', TokenKind::ampamp, '=', TokenKind::ampequal);
PUNC_L2_3('|', TokenKind::pipe, '|', TokenKind::pipepipe, '=', TokenKind::pipeequal);

// * *= ** **=
case '*':
token_.setStart(curCharPtr_);
if (curCharPtr_[1] == '=') {
token_.setPunctuator(TokenKind::starequal);
curCharPtr_ += 2;
} else if (curCharPtr_[1] != '*') {
token_.setPunctuator(TokenKind::star);
curCharPtr_ += 1;
} else if (curCharPtr_[2] == '=') {
token_.setPunctuator(TokenKind::starstarequal);
curCharPtr_ += 3;
} else {
token_.setPunctuator(TokenKind::starstar);
curCharPtr_ += 2;
}
break;

// * *=
// % %=
// ^ ^=
// / /=
PUNC_L2_2('*', TokenKind::star, '=', TokenKind::starequal);
PUNC_L2_2('%', TokenKind::percent, '=', TokenKind::percentequal);
PUNC_L2_2('^', TokenKind::caret, '=', TokenKind::caretequal);

Expand Down
26 changes: 24 additions & 2 deletions lib/Parser/JSParserImpl.cpp
Expand Up @@ -202,6 +202,7 @@ bool JSParserImpl::checkAssign() const {
TokenKind::lesslessequal,
TokenKind::greatergreaterequal,
TokenKind::greatergreatergreaterequal,
TokenKind::starstarequal,
TokenKind::ampequal,
TokenKind::caretequal,
TokenKind::pipeequal);
Expand Down Expand Up @@ -2688,6 +2689,15 @@ Optional<ESTree::Node *> JSParserImpl::parseUnaryExpression() {
if (!expr)
return None;

if (check(TokenKind::starstar)) {
// ExponentiationExpression only allows UpdateExpressionNode on the
// left. The simplest way to enforce that the left operand is not
// an unparenthesized UnaryExpression is to check here.
sm_.error(
{startLoc, tok_->getEndLoc()},
"Unary operator before ** must use parens to disambiguate");
}

return setLocation(
startLoc,
expr.getValue(),
Expand Down Expand Up @@ -2738,6 +2748,11 @@ inline unsigned getPrecedence(TokenKind kind) {
return precedence[static_cast<unsigned>(kind)];
}

/// \return true if \p kind is left associative, false if right associative.
inline bool isLeftAssoc(TokenKind kind) {
return kind != TokenKind::starstar;
}

/// Return the precedence of \p kind unless it happens to be equal to \p except,
/// in which case return 0.
inline unsigned getPrecedenceExcept(TokenKind kind, TokenKind except) {
Expand Down Expand Up @@ -2770,9 +2785,16 @@ Optional<ESTree::Node *> JSParserImpl::parseBinaryExpression(Param param) {
// While the current token is a binary operator.
while (unsigned precedence =
getPrecedenceExcept(tok_->getKind(), exceptKind)) {
// If the next operator has lower precedence than the operator on the stack,
// pop the stack, creating a new binary expression.
// If the next operator has no greater precedence than the operator on the
// stack, pop the stack, creating a new binary expression.
while (sp != STACK_SIZE && precedence <= getPrecedence(opStack[sp])) {
if (precedence == getPrecedence(opStack[sp]) &&
!isLeftAssoc(opStack[sp])) {
// If the precedences are equal, then we avoid popping for
// right-associative operators to allow for the entire right-associative
// expression to be built from the right.
break;
}
topExpr = newBinNode(valueStack[sp], opStack[sp], topExpr);
++sp;
}
Expand Down
1 change: 1 addition & 0 deletions lib/VM/JSLib/HermesInternal.cpp
Expand Up @@ -871,6 +871,7 @@ Handle<JSObject> createHermesInternalObject(Runtime *runtime) {
defineInternMethod(P::ttiReached, hermesInternalTTIReached);
defineInternMethod(P::ttrcReached, hermesInternalTTRCReached);
defineInternMethod(P::exportAll, hermesInternalExportAll);
defineInternMethod(P::exponentiationOperator, mathPow);
#ifdef HERMESVM_EXCEPTION_ON_OOM
defineInternMethodAndSymbol("getCallStack", hermesInternalGetCallStack, 0);
#endif // HERMESVM_EXCEPTION_ON_OOM
Expand Down
1 change: 1 addition & 0 deletions lib/VM/JSLib/Math.cpp
Expand Up @@ -19,6 +19,7 @@
#include <float.h>
#include <math.h>
#include <random>
#include "hermes/Support/Math.h"
#include "hermes/Support/OSCompat.h"

#include "llvm/Support/MathExtras.h"
Expand Down
4 changes: 3 additions & 1 deletion test/Optimizer/simplify.js
Expand Up @@ -599,12 +599,14 @@ function equality(x, y) {
//CHECK-NEXT: %0 = TryLoadGlobalPropertyInst globalObject : object, "print" : string
//CHECK-NEXT: %1 = CallInst %0, undefined : undefined, 4 : number
//CHECK-NEXT: %2 = CallInst %0, undefined : undefined, 8 : number
//CHECK-NEXT: %3 = ReturnInst undefined : undefined
//CHECK-NEXT: %3 = CallInst %0, undefined : undefined, 64 : number
//CHECK-NEXT: %4 = ReturnInst undefined : undefined
//CHECK-NEXT:function_end
function arith() {
var sink = print;
sink(2 * 2)
sink(2 * 4)
sink(2 ** 6)
}


Expand Down
16 changes: 16 additions & 0 deletions test/Parser/exp-error.js
@@ -0,0 +1,16 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the LICENSE
// file in the root directory of this source tree.
//
// RUN: (! %hermesc -dump-ast -pretty-json %s 2>&1 ) | %FileCheck --match-full-lines %s

+3 ** 2;
// CHECK: {{.*}}:8:1: error: Unary operator before ** must use parens to disambiguate
// CHECK: +3 ** 2;
// CHECK: ^~~~~

delete 3 ** 2;
// CHECK: {{.*}}:13:1: error: Unary operator before ** must use parens to disambiguate
// CHECK: delete 3 ** 2;
// CHECK: ^~~~~~~~~~~

0 comments on commit ae0bc93

Please sign in to comment.