lib/IRGen/ESTreeIRGen-expr.cpp (1,317 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "ESTreeIRGen.h" #include "llvh/ADT/ScopeExit.h" namespace hermes { namespace irgen { Value *ESTreeIRGen::genExpression(ESTree::Node *expr, Identifier nameHint) { LLVM_DEBUG( dbgs() << "IRGen expression of type " << expr->getNodeName() << "\n"); IRBuilder::ScopedLocationChange slc(Builder, expr->getDebugLoc()); // Handle identifiers. if (auto *Iden = llvh::dyn_cast<ESTree::IdentifierNode>(expr)) { return genIdentifierExpression(Iden, false); } // Handle Null Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-null-literals if (llvh::isa<ESTree::NullLiteralNode>(expr)) { return Builder.getLiteralNull(); } // Handle String Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-string-literals if (auto *Lit = llvh::dyn_cast<ESTree::StringLiteralNode>(expr)) { LLVM_DEBUG(dbgs() << "Loading String Literal \"" << Lit->_value << "\"\n"); return Builder.getLiteralString(Lit->_value->str()); } // Handle Regexp Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-regular-expression-literals if (auto *Lit = llvh::dyn_cast<ESTree::RegExpLiteralNode>(expr)) { LLVM_DEBUG( dbgs() << "Loading regexp Literal \"" << Lit->_pattern->str() << " / " << Lit->_flags->str() << "\"\n"); return Builder.createRegExpInst( Identifier::getFromPointer(Lit->_pattern), Identifier::getFromPointer(Lit->_flags)); } // Handle Boolean Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-boolean-literals if (auto *Lit = llvh::dyn_cast<ESTree::BooleanLiteralNode>(expr)) { LLVM_DEBUG(dbgs() << "Loading String Literal \"" << Lit->_value << "\"\n"); return Builder.getLiteralBool(Lit->_value); } // Handle Number Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-numeric-literals if (auto *Lit = llvh::dyn_cast<ESTree::NumericLiteralNode>(expr)) { LLVM_DEBUG(dbgs() << "Loading Numeric Literal \"" << Lit->_value << "\"\n"); return Builder.getLiteralNumber(Lit->_value); } // Handle the assignment expression. if (auto Assign = llvh::dyn_cast<ESTree::AssignmentExpressionNode>(expr)) { return genAssignmentExpr(Assign); } // Handle Call expressions. if (auto *call = llvh::dyn_cast<ESTree::CallExpressionNode>(expr)) { return genCallExpr(call); } // Handle Call expressions. if (auto *call = llvh::dyn_cast<ESTree::OptionalCallExpressionNode>(expr)) { return genOptionalCallExpr(call, nullptr); } // Handle the 'new' expressions. if (auto *newExp = llvh::dyn_cast<ESTree::NewExpressionNode>(expr)) { return genNewExpr(newExp); } // Handle MemberExpression expressions for access property. if (auto *Mem = llvh::dyn_cast<ESTree::MemberExpressionNode>(expr)) { return genMemberExpression(Mem, MemberExpressionOperation::Load).result; } // Handle MemberExpression expressions for access property. if (auto *mem = llvh::dyn_cast<ESTree::OptionalMemberExpressionNode>(expr)) { return genOptionalMemberExpression( mem, nullptr, MemberExpressionOperation::Load) .result; } // Handle Array expressions (syntax: [1,2,3]). if (auto *Arr = llvh::dyn_cast<ESTree::ArrayExpressionNode>(expr)) { return genArrayExpr(Arr); } // Handle object expressions (syntax: {"1" : "2"}). if (auto *Obj = llvh::dyn_cast<ESTree::ObjectExpressionNode>(expr)) { return genObjectExpr(Obj); } // Handle logical expressions (short circuiting). if (auto *L = llvh::dyn_cast<ESTree::LogicalExpressionNode>(expr)) { return genLogicalExpression(L); } // Handle Binary Expressions. if (auto *Bin = llvh::dyn_cast<ESTree::BinaryExpressionNode>(expr)) { return genBinaryExpression(Bin); } // Handle Unary operator Expressions. if (auto *U = llvh::dyn_cast<ESTree::UnaryExpressionNode>(expr)) { return genUnaryExpression(U); } // Handle the 'this' keyword. if (llvh::isa<ESTree::ThisExpressionNode>(expr)) { if (curFunction()->function->getDefinitionKind() == Function::DefinitionKind::ES6Arrow) { assert( curFunction()->capturedThis && "arrow function must have a captured this"); return Builder.createLoadFrameInst(curFunction()->capturedThis); } return curFunction()->function->getThisParameter(); } if (auto *MP = llvh::dyn_cast<ESTree::MetaPropertyNode>(expr)) { return genMetaProperty(MP); } // Handle function expressions. if (auto *FE = llvh::dyn_cast<ESTree::FunctionExpressionNode>(expr)) { return genFunctionExpression(FE, nameHint); } if (auto *AF = llvh::dyn_cast<ESTree::ArrowFunctionExpressionNode>(expr)) { return genArrowFunctionExpression(AF, nameHint); } if (auto *U = llvh::dyn_cast<ESTree::UpdateExpressionNode>(expr)) { return genUpdateExpr(U); } if (auto *C = llvh::dyn_cast<ESTree::ConditionalExpressionNode>(expr)) { return genConditionalExpr(C); } if (auto *Sq = llvh::dyn_cast<ESTree::SequenceExpressionNode>(expr)) { return genSequenceExpr(Sq); } if (auto *Tl = llvh::dyn_cast<ESTree::TemplateLiteralNode>(expr)) { return genTemplateLiteralExpr(Tl); } if (auto *Tt = llvh::dyn_cast<ESTree::TaggedTemplateExpressionNode>(expr)) { return genTaggedTemplateExpr(Tt); } if (auto *Y = llvh::dyn_cast<ESTree::YieldExpressionNode>(expr)) { return Y->_delegate ? genYieldStarExpr(Y) : genYieldExpr(Y); } if (auto *A = llvh::dyn_cast<ESTree::AwaitExpressionNode>(expr)) { return genAwaitExpr(A); } Builder.getModule()->getContext().getSourceErrorManager().error( expr->getSourceRange(), Twine("Invalid expression encountered")); return Builder.getLiteralUndefined(); } void ESTreeIRGen::genExpressionBranch( ESTree::Node *expr, BasicBlock *onTrue, BasicBlock *onFalse, BasicBlock *onNullish) { switch (expr->getKind()) { case ESTree::NodeKind::LogicalExpression: return genLogicalExpressionBranch( cast<ESTree::LogicalExpressionNode>(expr), onTrue, onFalse, onNullish); case ESTree::NodeKind::UnaryExpression: { auto *e = cast<ESTree::UnaryExpressionNode>(expr); switch (UnaryOperatorInst::parseOperator(e->_operator->str())) { case UnaryOperatorInst::OpKind::BangKind: // Do not propagate onNullish here because !expr cannot be nullish. return genExpressionBranch(e->_argument, onFalse, onTrue, nullptr); default: break; } break; } case ESTree::NodeKind::SequenceExpression: { auto *e = cast<ESTree::SequenceExpressionNode>(expr); ESTree::NodePtr last = nullptr; for (auto &ex : e->_expressions) { if (last) genExpression(last); last = &ex; } if (last) genExpressionBranch(last, onTrue, onFalse, onNullish); return; } default: break; } Value *condVal = genExpression(expr); if (onNullish) { Value *isNullish = Builder.createBinaryOperatorInst( condVal, Builder.getLiteralNull(), BinaryOperatorInst::OpKind::EqualKind); BasicBlock *notNullishBB = Builder.createBasicBlock(Builder.getFunction()); Builder.createCondBranchInst(isNullish, onNullish, notNullishBB); Builder.setInsertionBlock(notNullishBB); } Builder.createCondBranchInst(condVal, onTrue, onFalse); } Value *ESTreeIRGen::genArrayFromElements(ESTree::NodeList &list) { LLVM_DEBUG(dbgs() << "Initializing a new array\n"); AllocArrayInst::ArrayValueList elements; // Precalculate the minimum number of elements in case we need to call // AllocArrayInst at some point, as well as find out whether we have a spread // element (which will result in the final array having variable length). unsigned minElements = 0; bool variableLength = false; for (auto &E : list) { if (llvh::isa<ESTree::SpreadElementNode>(&E)) { variableLength = true; continue; } ++minElements; } // We store consecutive elements until we encounter elision, // or we enounter a non-literal in limited-register mode. // The rest of them has to be initialized through property sets. // If we have a variable length array, then we store the next index in // a stack location `nextIndex`, to be updated when we encounter spread // elements. Otherwise, we simply count them in `count`. unsigned count = 0; AllocStackInst *nextIndex = nullptr; if (variableLength) { // Avoid emitting the extra instructions unless we actually need to, // to simplify tests and because it's easy. nextIndex = Builder.createAllocStackInst("nextIndex"); Builder.createStoreStackInst(Builder.getLiteralPositiveZero(), nextIndex); } bool consecutive = true; auto codeGenOpts = Mod->getContext().getCodeGenerationSettings(); AllocArrayInst *allocArrayInst = nullptr; for (auto &E : list) { Value *value{nullptr}; bool isSpread = false; if (!llvh::isa<ESTree::EmptyNode>(&E)) { if (auto *spread = llvh::dyn_cast<ESTree::SpreadElementNode>(&E)) { isSpread = true; value = genExpression(spread->_argument); } else { value = genExpression(&E); } } if (!value || (!llvh::isa<Literal>(value) && !codeGenOpts.unlimitedRegisters) || isSpread) { // This is either an elision, // or a non-literal in limited-register mode, // or a spread element. if (consecutive) { // So far we have been storing elements consecutively, // but not anymore, time to create the array. allocArrayInst = Builder.createAllocArrayInst(elements, minElements); consecutive = false; } } if (isSpread) { // Spread the SpreadElement argument into the array. // HermesInternal.arraySpread returns the new value of nextIndex, // so update nextIndex accordingly and finish this iteration of the loop. auto *newNextIndex = genBuiltinCall( BuiltinMethod::HermesBuiltin_arraySpread, {allocArrayInst, value, Builder.createLoadStackInst(nextIndex)}); Builder.createStoreStackInst(newNextIndex, nextIndex); continue; } // The element is not a spread element, so perform the store here. if (value) { if (consecutive) { elements.push_back(value); } else { Builder.createStoreOwnPropertyInst( value, allocArrayInst, variableLength ? cast<Value>(Builder.createLoadStackInst(nextIndex)) : cast<Value>(Builder.getLiteralNumber(count)), IRBuilder::PropEnumerable::Yes); } } // Update the next index or the count depending on if it's a variable length // array. if (variableLength) { // We perform this update on any leading elements before the first spread // element as well, but the optimizer will eliminate the extra adds // because we know the initial value (0) and how incrementing works. Builder.createStoreStackInst( Builder.createBinaryOperatorInst( Builder.createLoadStackInst(nextIndex), Builder.getLiteralNumber(1), BinaryOperatorInst::OpKind::AddKind), nextIndex); } else { count++; } } if (!allocArrayInst) { assert( !variableLength && "variable length arrays must allocate their own arrays"); allocArrayInst = Builder.createAllocArrayInst(elements, list.size()); } if (count > 0 && llvh::isa<ESTree::EmptyNode>(&list.back())) { // Last element is an elision, VM cannot derive the length properly. // We have to explicitly set it. Builder.createStorePropertyInst( Builder.getLiteralNumber(count), allocArrayInst, StringRef("length")); } return allocArrayInst; } Value *ESTreeIRGen::genArrayExpr(ESTree::ArrayExpressionNode *Expr) { return genArrayFromElements(Expr->_elements); } Value *ESTreeIRGen::genCallExpr(ESTree::CallExpressionNode *call) { LLVM_DEBUG(dbgs() << "IRGen 'call' statement/expression.\n"); // Check for a direct call to eval(). if (auto *identNode = llvh::dyn_cast<ESTree::IdentifierNode>(call->_callee)) { if (Identifier::getFromPointer(identNode->_name) == identEval_) { auto *evalVar = nameTable_.lookup(identEval_); if (!evalVar || llvh::isa<GlobalObjectProperty>(evalVar)) return genCallEvalExpr(call); } } Value *thisVal; Value *callee; // Handle MemberExpression expression calls that sets the 'this' property. if (auto *Mem = llvh::dyn_cast<ESTree::MemberExpressionNode>(call->_callee)) { MemberExpressionResult memResult = genMemberExpression(Mem, MemberExpressionOperation::Load); // Call the callee with obj as the 'this' pointer. thisVal = memResult.base; callee = memResult.result; } else if ( auto *Mem = llvh::dyn_cast<ESTree::OptionalMemberExpressionNode>(call->_callee)) { MemberExpressionResult memResult = genOptionalMemberExpression( Mem, nullptr, MemberExpressionOperation::Load); // Call the callee with obj as the 'this' pointer. thisVal = memResult.base; callee = memResult.result; } else { thisVal = Builder.getLiteralUndefined(); callee = genExpression(call->_callee); } return emitCall(call, callee, thisVal); } Value *ESTreeIRGen::genOptionalCallExpr( ESTree::OptionalCallExpressionNode *call, BasicBlock *shortCircuitBB) { PhiInst::ValueListType values; PhiInst::BasicBlockListType blocks; // true when this is the genOptionalCallExpr call containing // the logic for shortCircuitBB. bool isFirstOptional = shortCircuitBB == nullptr; // If isFirstOptional, the final result will be computed in continueBB and // returned. BasicBlock *continueBB = nullptr; if (!shortCircuitBB) { // If shortCircuitBB is null, then this is the outermost in the optional // chain, so we must create it here and pass it through to every other // OptionalCallExpression and OptionalMemberExpression in the chain. continueBB = Builder.createBasicBlock(Builder.getFunction()); auto *insertionBB = Builder.getInsertionBlock(); shortCircuitBB = Builder.createBasicBlock(Builder.getFunction()); Builder.setInsertionBlock(shortCircuitBB); values.push_back(Builder.getLiteralUndefined()); blocks.push_back(shortCircuitBB); Builder.createBranchInst(continueBB); Builder.setInsertionBlock(insertionBB); } Value *thisVal; Value *callee; // Handle MemberExpression expression calls that sets the 'this' property. if (auto *me = llvh::dyn_cast<ESTree::MemberExpressionNode>(call->_callee)) { MemberExpressionResult memResult = genMemberExpression(me, MemberExpressionOperation::Load); // Call the callee with obj as the 'this' pointer. thisVal = memResult.base; callee = memResult.result; } else if ( auto *ome = llvh::dyn_cast<ESTree::OptionalMemberExpressionNode>(call->_callee)) { MemberExpressionResult memResult = genOptionalMemberExpression( ome, shortCircuitBB, MemberExpressionOperation::Load); // Call the callee with obj as the 'this' pointer. thisVal = memResult.base; callee = memResult.result; } else if ( auto *oce = llvh::dyn_cast<ESTree::OptionalCallExpressionNode>(call->_callee)) { thisVal = Builder.getLiteralUndefined(); callee = genOptionalCallExpr(oce, shortCircuitBB); } else { thisVal = Builder.getLiteralUndefined(); callee = genExpression(getCallee(call)); } if (call->_optional) { BasicBlock *evalRHSBB = Builder.createBasicBlock(Builder.getFunction()); // If callee is undefined or null, then return undefined. // NOTE: We use `obj == null` to account for both null and undefined. Builder.createCondBranchInst( Builder.createBinaryOperatorInst( callee, Builder.getLiteralNull(), BinaryOperatorInst::OpKind::EqualKind), shortCircuitBB, evalRHSBB); // baseValue is not undefined or null. Builder.setInsertionBlock(evalRHSBB); } Value *callResult = emitCall(call, callee, thisVal); if (isFirstOptional) { values.push_back(callResult); blocks.push_back(Builder.getInsertionBlock()); Builder.createBranchInst(continueBB); Builder.setInsertionBlock(continueBB); return Builder.createPhiInst(values, blocks); } // If this isn't the first optional, no Phi needed, just return the // callResult. return callResult; } Value *ESTreeIRGen::emitCall( ESTree::CallExpressionLikeNode *call, Value *callee, Value *thisVal) { bool hasSpread = false; for (auto &arg : getArguments(call)) { if (llvh::isa<ESTree::SpreadElementNode>(&arg)) { hasSpread = true; } } if (!hasSpread) { CallInst::ArgumentList args; for (auto &arg : getArguments(call)) { args.push_back(genExpression(&arg)); } return Builder.createCallInst(callee, thisVal, args); } // Otherwise, there exists a spread argument, so the number of arguments // is variable. // Generate IR for this by creating an array and populating it with the // arguments, then calling HermesInternal.apply. auto *args = genArrayFromElements(getArguments(call)); return genBuiltinCall( BuiltinMethod::HermesBuiltin_apply, {callee, args, thisVal}); } ESTreeIRGen::MemberExpressionResult ESTreeIRGen::genMemberExpression( ESTree::MemberExpressionNode *mem, MemberExpressionOperation op) { Value *baseValue = genExpression(mem->_object); Value *prop = genMemberExpressionProperty(mem); switch (op) { case MemberExpressionOperation::Load: return MemberExpressionResult{ Builder.createLoadPropertyInst(baseValue, prop), baseValue}; case MemberExpressionOperation::Delete: return MemberExpressionResult{ Builder.createDeletePropertyInst(baseValue, prop), baseValue}; } llvm_unreachable("No other kind of member expression"); } ESTreeIRGen::MemberExpressionResult ESTreeIRGen::genOptionalMemberExpression( ESTree::OptionalMemberExpressionNode *mem, BasicBlock *shortCircuitBB, MemberExpressionOperation op) { PhiInst::ValueListType values; PhiInst::BasicBlockListType blocks; // true when this is the genOptionalMemberExpression call containing // the logic for shortCircuitBB. bool isFirstOptional = shortCircuitBB == nullptr; // If isFirstOptional, the final result will be computed in continueBB and // returned. BasicBlock *continueBB = nullptr; if (isFirstOptional) { // If shortCircuitBB is null, then this is the outermost in the optional // chain, so we must create it here and pass it through to every other // OptionalCallExpression and OptionalMemberExpression in the chain. continueBB = Builder.createBasicBlock(Builder.getFunction()); auto *insertionBB = Builder.getInsertionBlock(); shortCircuitBB = Builder.createBasicBlock(Builder.getFunction()); Builder.setInsertionBlock(shortCircuitBB); values.push_back(Builder.getLiteralUndefined()); blocks.push_back(shortCircuitBB); Builder.createBranchInst(continueBB); Builder.setInsertionBlock(insertionBB); } Value *baseValue = nullptr; if (ESTree::OptionalMemberExpressionNode *ome = llvh::dyn_cast<ESTree::OptionalMemberExpressionNode>(mem->_object)) { baseValue = genOptionalMemberExpression( ome, shortCircuitBB, MemberExpressionOperation::Load) .result; } else if ( ESTree::OptionalCallExpressionNode *oce = llvh::dyn_cast<ESTree::OptionalCallExpressionNode>(mem->_object)) { baseValue = genOptionalCallExpr(oce, shortCircuitBB); } else { baseValue = genExpression(mem->_object); } if (mem->_optional) { BasicBlock *evalRHSBB = Builder.createBasicBlock(Builder.getFunction()); // If baseValue is undefined or null, then return undefined. // NOTE: We use `obj == null` to account for both null and undefined. Builder.createCondBranchInst( Builder.createBinaryOperatorInst( baseValue, Builder.getLiteralNull(), BinaryOperatorInst::OpKind::EqualKind), shortCircuitBB, evalRHSBB); // baseValue is not undefined, look up the property properly. Builder.setInsertionBlock(evalRHSBB); } Value *prop = genMemberExpressionProperty(mem); Value *result = nullptr; switch (op) { case MemberExpressionOperation::Load: result = Builder.createLoadPropertyInst(baseValue, prop); break; case MemberExpressionOperation::Delete: result = Builder.createDeletePropertyInst(baseValue, prop); break; } assert(result && "result must be set"); if (isFirstOptional) { values.push_back(result); blocks.push_back(Builder.getInsertionBlock()); Builder.createBranchInst(continueBB); Builder.setInsertionBlock(continueBB); return {Builder.createPhiInst(values, blocks), baseValue}; } // If this isn't the first optional, no Phi needed, just return the result. return {result, baseValue}; } Value *ESTreeIRGen::genCallEvalExpr(ESTree::CallExpressionNode *call) { if (call->_arguments.empty()) { Mod->getContext().getSourceErrorManager().warning( call->getSourceRange(), "eval() without arguments returns undefined"); return Builder.getLiteralUndefined(); } Mod->getContext().getSourceErrorManager().warning( Warning::DirectEval, call->getSourceRange(), "Direct call to eval(), but lexical scope is not supported."); llvh::SmallVector<Value *, 1> args; for (auto &arg : call->_arguments) { args.push_back(genExpression(&arg)); } if (args.size() > 1) { Mod->getContext().getSourceErrorManager().warning( call->getSourceRange(), "Extra eval() arguments are ignored"); } return Builder.createDirectEvalInst(args[0]); } /// Convert a property key node to its JavaScript string representation. static StringRef propertyKeyAsString( llvh::SmallVectorImpl<char> &storage, ESTree::Node *Key) { // Handle String Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-string-literals if (auto *Lit = llvh::dyn_cast<ESTree::StringLiteralNode>(Key)) { LLVM_DEBUG(dbgs() << "Loading String Literal \"" << Lit->_value << "\"\n"); return Lit->_value->str(); } // Handle identifiers as if they are String Literals. if (auto *Iden = llvh::dyn_cast<ESTree::IdentifierNode>(Key)) { LLVM_DEBUG(dbgs() << "Loading String Literal \"" << Iden->_name << "\"\n"); return Iden->_name->str(); } // Handle Number Literals. // http://www.ecma-international.org/ecma-262/6.0/#sec-literals-numeric-literals if (auto *Lit = llvh::dyn_cast<ESTree::NumericLiteralNode>(Key)) { LLVM_DEBUG(dbgs() << "Loading Numeric Literal \"" << Lit->_value << "\"\n"); storage.resize(NUMBER_TO_STRING_BUF_SIZE); auto len = numberToString(Lit->_value, storage.data(), storage.size()); return StringRef(storage.begin(), len); } llvm_unreachable("Don't know this kind of property key"); return StringRef(); } Value *ESTreeIRGen::genObjectExpr(ESTree::ObjectExpressionNode *Expr) { LLVM_DEBUG(dbgs() << "Initializing a new object\n"); /// Store information about a property. Is it an accessor (getter/setter) or /// a value, and the actual value. class PropertyValue { public: /// Is this a getter/setter value. bool isAccessor = false; /// Tracks the state of generating IR for this value. enum { None, Placeholder, IRGenerated } state{None}; /// The value, if this is a regular property ESTree::Node *valueNode{}; /// Getter accessor, if this is an accessor property. ESTree::FunctionExpressionNode *getterNode{}; /// Setter accessor, if this is an accessor property. ESTree::FunctionExpressionNode *setterNode{}; SMRange getSourceRange() { if (valueNode) { return valueNode->getSourceRange(); } if (getterNode) { return getterNode->getSourceRange(); } if (setterNode) { return setterNode->getSourceRange(); } llvm_unreachable("Unset node has no location info"); } void setValue(ESTree::Node *val) { isAccessor = false; valueNode = val; getterNode = setterNode = nullptr; } void setGetter(ESTree::FunctionExpressionNode *get) { if (!isAccessor) { valueNode = nullptr; setterNode = nullptr; isAccessor = true; } getterNode = get; } void setSetter(ESTree::FunctionExpressionNode *set) { if (!isAccessor) { valueNode = nullptr; getterNode = nullptr; isAccessor = true; } setterNode = set; } }; // First accumulate all getters and setters. We walk all properties, convert // them to string and store the value into propMap, possibly overwriting the // previous value. // Note that computed properties are not stored in the propMap as we do not // know the keys at compilation time. llvh::StringMap<PropertyValue> propMap; // The first location where a given property name is encountered. llvh::StringMap<SMRange> firstLocMap; llvh::SmallVector<char, 32> stringStorage; /// The optional __proto__ property. ESTree::PropertyNode *protoProperty = nullptr; uint32_t numComputed = 0; bool hasSpread = false; bool hasAccessor = false; bool hasDuplicateProperty = false; for (auto &P : Expr->_properties) { if (llvh::isa<ESTree::SpreadElementNode>(&P)) { hasSpread = true; continue; } // We are reusing the storage, so make sure it is cleared at every // iteration. stringStorage.clear(); auto *prop = cast<ESTree::PropertyNode>(&P); if (prop->_computed) { // Can't store any useful information if the name is computed. // Just generate the code in the next loop. ++numComputed; continue; } auto propName = propertyKeyAsString(stringStorage, prop->_key); // protoProperty should only be recorded if the property is not a method // nor a shorthand value. if (prop->_kind->str() == "init" && propName == "__proto__" && !prop->_method && !prop->_shorthand) { if (!protoProperty) { protoProperty = prop; } else { Builder.getModule()->getContext().getSourceErrorManager().error( prop->getSourceRange(), "__proto__ was set multiple times in the object definition."); Builder.getModule()->getContext().getSourceErrorManager().note( protoProperty->getSourceRange(), "The first definition was here."); } continue; } PropertyValue *propValue = &propMap[propName]; if (prop->_kind->str() == "get") { propValue->setGetter(cast<ESTree::FunctionExpressionNode>(prop->_value)); hasAccessor = true; } else if (prop->_kind->str() == "set") { propValue->setSetter(cast<ESTree::FunctionExpressionNode>(prop->_value)); hasAccessor = true; } else { assert(prop->_kind->str() == "init" && "invalid PropertyNode kind"); // We record the propValue if this is a regular property propValue->setValue(prop->_value); } std::string key = (prop->_kind->str() + propName).str(); auto iterAndSuccess = firstLocMap.try_emplace(key, prop->getSourceRange()); if (!iterAndSuccess.second) { hasDuplicateProperty = true; Builder.getModule()->getContext().getSourceErrorManager().warning( prop->getSourceRange(), Twine("the property \"") + propName + "\" was set multiple times in the object definition."); Builder.getModule()->getContext().getSourceErrorManager().note( iterAndSuccess.first->second, "The first definition was here."); } } // Heuristically determine if we emit AllocObjectLiteral. // We do so if there is no computed key, no __proto__, no spread element // node, no duplicate properties, no accessors, and object literal is not // empty. if (numComputed == 0 && !protoProperty && !hasSpread && !hasDuplicateProperty && !hasAccessor && propMap.size()) { AllocObjectLiteralInst::ObjectPropertyMap objPropMap; // It is safe to assume that there is no computed keys, and // no __proto__. for (auto &P : Expr->_properties) { auto *prop = cast<ESTree::PropertyNode>(&P); assert( !prop->_computed && "Cannot handle computed key in AllocObjectLiteral"); // We are reusing the storage, so make sure it is cleared at every // iteration. stringStorage.clear(); StringRef keyStr = propertyKeyAsString(stringStorage, prop->_key); auto *Key = Builder.getLiteralString(keyStr); assert( propMap[keyStr].valueNode == prop->_value && "Should only have one value for each property."); auto value = genExpression(prop->_value, Builder.createIdentifier(keyStr)); objPropMap.push_back(std::pair<LiteralString *, Value *>(Key, value)); } return Builder.createAllocObjectLiteralInst(objPropMap); } /// Attempt to determine whether we can directly use the value of the /// __proto__ property as a parent when creating the object for an object /// initializer, instead of setting it later with /// HermesInternal.silentSetPrototypeOf(). That is not possible to determine /// statically in the general case, but we can check for the simple cases: /// - __proto__ property is first. /// - the value of __proto__ is constant. Value *objectParent = nullptr; if (protoProperty && (&Expr->_properties.front() == protoProperty || isConstantExpr(protoProperty->_value))) { objectParent = genExpression(protoProperty->_value); } // Allocate a new javascript object on the heap. auto Obj = Builder.createAllocObjectInst(propMap.size() + numComputed, objectParent); // haveSeenComputedProp tracks whether we have processed a computed property. // Once we do, for all future properties, we can no longer generate // StoreNewOwnPropertyInst because the computed property could have already // defined any property. bool haveSeenComputedProp = false; // Initialize all properties. We check whether the value of each property // will be overwritten (by comparing against what we have saved in propMap). // In that case we still compute the value (it could have side effects), but // we don't store it. The exception to this are accessor functions - there // is no need to create them if we don't use them because creating a function // has no side effects. for (auto &P : Expr->_properties) { if (auto *spread = llvh::dyn_cast<ESTree::SpreadElementNode>(&P)) { genBuiltinCall( BuiltinMethod::HermesBuiltin_copyDataProperties, {Obj, genExpression(spread->_argument)}); haveSeenComputedProp = true; continue; } // We are reusing the storage, so make sure it is cleared at every // iteration. stringStorage.clear(); auto *prop = cast<ESTree::PropertyNode>(&P); if (prop->_computed) { // TODO (T46136220): Set the .name property for anonymous functions that // are values for computed property keys. auto *key = genExpression(prop->_key); auto *value = genExpression(prop->_value); if (prop->_kind->str() == "get") { Builder.createStoreGetterSetterInst( value, Builder.getLiteralUndefined(), Obj, key, IRBuilder::PropEnumerable::Yes); } else if (prop->_kind->str() == "set") { Builder.createStoreGetterSetterInst( Builder.getLiteralUndefined(), value, Obj, key, IRBuilder::PropEnumerable::Yes); } else { Builder.createStoreOwnPropertyInst( value, Obj, key, IRBuilder::PropEnumerable::Yes); } haveSeenComputedProp = true; continue; } StringRef keyStr = propertyKeyAsString(stringStorage, prop->_key); if (prop == protoProperty) { // This is the first definition of __proto__. If we already used it // as an object parent we just skip it, but otherwise we must // explicitly set the parent now by calling \c // HermesInternal.silentSetPrototypeOf(). if (!objectParent) { auto *parent = genExpression(prop->_value); IRBuilder::SaveRestore saveState{Builder}; Builder.setLocation(prop->_key->getDebugLoc()); genBuiltinCall( BuiltinMethod::HermesBuiltin_silentSetPrototypeOf, {Obj, parent}); } continue; } PropertyValue *propValue = &propMap[keyStr]; // For any node that has a corresponding propValue, we need to ensure that // the we insert either a placeholder or the final IR before the end of this // iteration. auto checkState = llvh::make_scope_exit( [&] { assert(propValue->state != PropertyValue::None); }); auto *Key = Builder.getLiteralString(keyStr); auto maybeInsertPlaceholder = [&] { if (propValue->state == PropertyValue::None) { // This value is going to be overwritten, but insert a placeholder in // order to maintain insertion order. if (haveSeenComputedProp) { Builder.createStoreOwnPropertyInst( Builder.getLiteralNull(), Obj, Key, IRBuilder::PropEnumerable::Yes); } else { Builder.createStoreNewOwnPropertyInst( Builder.getLiteralNull(), Obj, Key, IRBuilder::PropEnumerable::Yes); } propValue->state = PropertyValue::Placeholder; } }; if (prop->_kind->str() == "get" || prop->_kind->str() == "set") { // If we already generated it, skip. if (propValue->state == PropertyValue::IRGenerated) continue; if (!propValue->isAccessor) { maybeInsertPlaceholder(); continue; } Value *getter = Builder.getLiteralUndefined(); Value *setter = Builder.getLiteralUndefined(); if (propValue->getterNode) { getter = genExpression( propValue->getterNode, Builder.createIdentifier("get " + keyStr.str())); } if (propValue->setterNode) { setter = genExpression( propValue->setterNode, Builder.createIdentifier("set " + keyStr.str())); } Builder.createStoreGetterSetterInst( getter, setter, Obj, Key, IRBuilder::PropEnumerable::Yes); propValue->state = PropertyValue::IRGenerated; continue; } // Always generate the values, even if we don't need it, for the side // effects. auto value = genExpression(prop->_value, Builder.createIdentifier(keyStr)); // Only store the value if it won't be overwritten. if (propMap[keyStr].valueNode == prop->_value) { assert( propValue->state != PropertyValue::IRGenerated && "IR can only be generated once"); if (haveSeenComputedProp || propValue->state == PropertyValue::Placeholder) { Builder.createStoreOwnPropertyInst( value, Obj, Key, IRBuilder::PropEnumerable::Yes); } else { Builder.createStoreNewOwnPropertyInst( value, Obj, Key, IRBuilder::PropEnumerable::Yes); } propValue->state = PropertyValue::IRGenerated; } else { maybeInsertPlaceholder(); } } // Return the newly allocated object (because this is an expression, not a // statement). return Obj; } Value *ESTreeIRGen::genSequenceExpr(ESTree::SequenceExpressionNode *Sq) { Value *result = Builder.getLiteralUndefined(); // Generate all expressions in the sequence, but take only the last one. for (auto &Ex : Sq->_expressions) { result = genExpression(&Ex); } return result; } Value *ESTreeIRGen::genYieldExpr(ESTree::YieldExpressionNode *Y) { assert(!Y->_delegate && "must use genYieldStarExpr for yield*"); Value *value = Y->_argument ? genExpression(Y->_argument) : Builder.getLiteralUndefined(); return genYieldOrAwaitExpr(value); } Value *ESTreeIRGen::genAwaitExpr(ESTree::AwaitExpressionNode *A) { Value *value = genExpression(A->_argument); return genYieldOrAwaitExpr(value); } Value *ESTreeIRGen::genYieldOrAwaitExpr(Value *value) { auto *bb = Builder.getInsertionBlock(); auto *next = Builder.createBasicBlock(bb->getParent()); auto *resumeIsReturn = Builder.createAllocStackInst(genAnonymousLabelName("isReturn")); Builder.createSaveAndYieldInst(value, next); Builder.setInsertionBlock(next); return genResumeGenerator( GenFinally::Yes, resumeIsReturn, Builder.createBasicBlock(bb->getParent())); } /// Generate the code for `yield* value`. /// We use some stack locations to store state while iterating: /// - received (the value passed by the user to .next(), etc) /// - result (the final result of the yield* expression) /// /// iteratorRecord stores the iterator which we are iterating over. /// /// Final IR has the following basic blocks for normal control flow: /// /// getNext: Get the next value from the iterator. /// - Call next() on the iteratorRecord and stores to `result` /// - If done, go to exit /// - Otherwise, go to body /// /// resume: Runs the ResumeGenerator instruction. /// - Code for `finally` is also emitted here. /// /// body: Yield the result of the next() call /// - Calls HermesInternal.generatorSetDelegated so that the result is not /// wrapped by the VM in an IterResult object. /// /// exit: Returns `result` which should have the final results stored in it. /// /// When the user calls `.return`, the finalizer is executed to call /// `iteratorRecord.return` if it exists. The code for that is contained within /// the SurroundingTry. If the .return function is defined, it is called and the /// 'done' property of the result of the call is used to either branch back to /// the 'resume' block or to propagate the return. /// /// When the user calls '.throw', the code in emitHandler is executed. All /// generators used as delegates must have a .throw() method, so that is checked /// for and called. The result is then used to either resume if not done, or to /// return immediately by branching to the 'exit' block. Value *ESTreeIRGen::genYieldStarExpr(ESTree::YieldExpressionNode *Y) { assert(Y->_delegate && "must use genYieldExpr for yield"); auto *function = Builder.getInsertionBlock()->getParent(); auto *getNextBlock = Builder.createBasicBlock(function); auto *bodyBlock = Builder.createBasicBlock(function); auto *exitBlock = Builder.createBasicBlock(function); // Calls ResumeGenerator and returns or throws if requested. auto *resumeBB = Builder.createBasicBlock(function); auto *exprValue = genExpression(Y->_argument); IteratorRecordSlow iteratorRecord = emitGetIteratorSlow(exprValue); // The "received" value when the user resumes the generator. // Initialized to undefined on the first run, then stored to immediately // following any genResumeGenerator. auto *received = Builder.createAllocStackInst(genAnonymousLabelName("received")); Builder.createStoreStackInst(Builder.getLiteralUndefined(), received); // The "isReturn" value when the user resumes the generator. // Stored to immediately following any genResumeGenerator. auto *resumeIsReturn = Builder.createAllocStackInst(genAnonymousLabelName("isReturn")); // The final result of the `yield*` expression. // This can be set from either the body or the handler, so it is placed // in the stack to allow populating it from anywhere. auto *result = Builder.createAllocStackInst(genAnonymousLabelName("result")); Builder.createBranchInst(getNextBlock); // 7.a.i. Let innerResult be ? Call( // iteratorRecord.[[NextMethod]], // iteratorRecord.[[Iterator]], // <received.[[Value]]> // ) // Avoid using emitIteratorNext here because the spec does not. Builder.setInsertionBlock(getNextBlock); auto *nextResult = Builder.createCallInst( iteratorRecord.nextMethod, iteratorRecord.iterator, {Builder.createLoadStackInst(received)}); emitEnsureObject(nextResult, "iterator.next() did not return an object"); Builder.createStoreStackInst(nextResult, result); auto *done = emitIteratorCompleteSlow(nextResult); Builder.createCondBranchInst(done, exitBlock, bodyBlock); Builder.setInsertionBlock(bodyBlock); emitTryCatchScaffolding( getNextBlock, // emitBody. [this, Y, resumeIsReturn, getNextBlock, resumeBB, nextResult, received, &iteratorRecord]() { // Generate IR for the body of Try SurroundingTry thisTry{ curFunction(), Y, {}, [this, resumeBB, received, &iteratorRecord]( ESTree::Node *, ControlFlowChange cfc, BasicBlock *) { if (cfc == ControlFlowChange::Break) { // This finalizer block is executed upon early return during // the yield*, which happens when the user requests a .return(). auto *function = Builder.getFunction(); auto *haveReturnBB = Builder.createBasicBlock(function); auto *noReturnBB = Builder.createBasicBlock(function); auto *isDoneBB = Builder.createBasicBlock(function); auto *isNotDoneBB = Builder.createBasicBlock(function); // Check if "returnMethod" is undefined. auto *returnMethod = genBuiltinCall( BuiltinMethod::HermesBuiltin_getMethod, {iteratorRecord.iterator, Builder.getLiteralString("return")}); Builder.createCompareBranchInst( returnMethod, Builder.getLiteralUndefined(), BinaryOperatorInst::OpKind::StrictlyEqualKind, noReturnBB, haveReturnBB); Builder.setInsertionBlock(haveReturnBB); // iv. Let innerReturnResult be // ? Call(return, iterator, received.[[Value]]). auto *innerReturnResult = Builder.createCallInst( returnMethod, iteratorRecord.iterator, {Builder.createLoadStackInst(received)}); // vi. If Type(innerReturnResult) is not Object, // throw a TypeError exception. emitEnsureObject( innerReturnResult, "iterator.return() did not return an object"); // vii. Let done be ? IteratorComplete(innerReturnResult). auto *done = emitIteratorCompleteSlow(innerReturnResult); Builder.createCondBranchInst(done, isDoneBB, isNotDoneBB); Builder.setInsertionBlock(isDoneBB); // viii. 1. Let value be ? IteratorValue(innerReturnResult). auto *value = emitIteratorValueSlow(innerReturnResult); genFinallyBeforeControlChange( curFunction()->surroundingTry, nullptr, ControlFlowChange::Break); // viii. 2. Return Completion // { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. Builder.createReturnInst(value); // x. Else, set received to GeneratorYield(innerReturnResult). Builder.setInsertionBlock(isNotDoneBB); genBuiltinCall( BuiltinMethod::HermesBuiltin_generatorSetDelegated, {}); Builder.createSaveAndYieldInst(innerReturnResult, resumeBB); // If return is undefined, return Completion(received). Builder.setInsertionBlock(noReturnBB); } }}; // The primary call path for yielding the next result. genBuiltinCall(BuiltinMethod::HermesBuiltin_generatorSetDelegated, {}); Builder.createSaveAndYieldInst(nextResult, resumeBB); // Note that resumeBB was created above to allow all SaveAndYield insts // to have the same resume point (including SaveAndYield in the catch // handler), but we must populate it inside the scaffolding so that the // SurroundingTry is correct for the genFinallyBeforeControlChange // call emitted by genResumeGenerator. Builder.setInsertionBlock(resumeBB); genResumeGenerator( GenFinally::Yes, resumeIsReturn, getNextBlock, received); // SaveAndYieldInst is a Terminator, but emitTryCatchScaffolding // needs a block from which to Branch to the TryEnd instruction. // Make a dummy block which can do that. Builder.setInsertionBlock( Builder.createBasicBlock(Builder.getFunction())); }, // emitNormalCleanup. []() {}, // emitHandler. [this, resumeBB, exitBlock, result, &iteratorRecord]( BasicBlock *getNextBlock) { auto *catchReg = Builder.createCatchInst(); auto *function = Builder.getFunction(); auto *hasThrowMethodBB = Builder.createBasicBlock(function); auto *noThrowMethodBB = Builder.createBasicBlock(function); auto *isDoneBB = Builder.createBasicBlock(function); auto *isNotDoneBB = Builder.createBasicBlock(function); // b.i. Let throw be ? GetMethod(iterator, "throw"). auto *throwMethod = genBuiltinCall( BuiltinMethod::HermesBuiltin_getMethod, {iteratorRecord.iterator, Builder.getLiteralString("throw")}); Builder.createCompareBranchInst( throwMethod, Builder.getLiteralUndefined(), BinaryOperatorInst::OpKind::StrictlyEqualKind, noThrowMethodBB, hasThrowMethodBB); // ii. If throw is not undefined, then Builder.setInsertionBlock(hasThrowMethodBB); // ii. 1. Let innerResult be // ? Call(throw, iterator, « received.[[Value]] »). // ii. 3. NOTE: Exceptions from the inner iterator throw method are // propagated. Normal completions from an inner throw method are // processed similarly to an inner next. auto *innerResult = Builder.createCallInst( throwMethod, iteratorRecord.iterator, {catchReg}); // ii. 4. If Type(innerResult) is not Object, // throw a TypeError exception. emitEnsureObject( innerResult, "iterator.throw() did not return an object"); // ii. 5. Let done be ? IteratorComplete(innerResult). auto *done = emitIteratorCompleteSlow(innerResult); Builder.createCondBranchInst(done, isDoneBB, isNotDoneBB); // ii. 6. If done is true, then return ? IteratorValue(innerResult). Builder.setInsertionBlock(isDoneBB); Builder.createStoreStackInst(innerResult, result); Builder.createBranchInst(exitBlock); // ii. 8. Else, set received to GeneratorYield(innerResult). Builder.setInsertionBlock(isNotDoneBB); genBuiltinCall(BuiltinMethod::HermesBuiltin_generatorSetDelegated, {}); Builder.createSaveAndYieldInst(innerResult, resumeBB); // NOTE: If iterator does not have a throw method, this throw is // going to terminate the yield* loop. But first we need to give // iterator a chance to clean up. Builder.setInsertionBlock(noThrowMethodBB); emitIteratorCloseSlow(iteratorRecord, false); genBuiltinCall( BuiltinMethod::HermesBuiltin_throwTypeError, {Builder.getLiteralString( "yield* delegate must have a .throw() method")}); // HermesInternal.throwTypeError will necessarily throw, but we need to // have a terminator on this BB to allow proper optimization. Builder.createReturnInst(Builder.getLiteralUndefined()); }); Builder.setInsertionBlock(exitBlock); return emitIteratorValueSlow(Builder.createLoadStackInst(result)); } Value *ESTreeIRGen::genResumeGenerator( GenFinally genFinally, AllocStackInst *isReturn, BasicBlock *nextBB, AllocStackInst *received) { auto *resume = Builder.createResumeGeneratorInst(isReturn); if (received) { Builder.createStoreStackInst(resume, received); } auto *retBB = Builder.createBasicBlock(Builder.getInsertionBlock()->getParent()); Builder.createCondBranchInst( Builder.createLoadStackInst(isReturn), retBB, nextBB); Builder.setInsertionBlock(retBB); if (received) { Builder.createStoreStackInst(resume, received); } if (genFinally == GenFinally::Yes) { genFinallyBeforeControlChange( curFunction()->surroundingTry, nullptr, ControlFlowChange::Break); } Builder.createReturnInst(resume); Builder.setInsertionBlock(nextBB); return resume; } Value *ESTreeIRGen::genBinaryExpression(ESTree::BinaryExpressionNode *bin) { // Handle long chains of +/- non-recursively. if (bin->_operator->str() == "+" || bin->_operator->str() == "-") { auto list = linearizeLeft(bin, {"+", "-"}); Value *LHS = genExpression(list[0]->_left); for (auto *e : list) { Value *RHS = genExpression(e->_right); Builder.setLocation(e->getDebugLoc()); auto cookie = instrumentIR_.preBinaryExpression(e, LHS, RHS); auto Kind = BinaryOperatorInst::parseOperator(e->_operator->str()); BinaryOperatorInst *result = Builder.createBinaryOperatorInst(LHS, RHS, Kind); LHS = instrumentIR_.postBinaryExpression(e, cookie, result, LHS, RHS); } return LHS; } Value *LHS = genExpression(bin->_left); Value *RHS = genExpression(bin->_right); auto cookie = instrumentIR_.preBinaryExpression(bin, LHS, RHS); auto Kind = BinaryOperatorInst::parseOperator(bin->_operator->str()); BinaryOperatorInst *result = Builder.createBinaryOperatorInst(LHS, RHS, Kind); return instrumentIR_.postBinaryExpression(bin, cookie, result, LHS, RHS); } Value *ESTreeIRGen::genUnaryExpression(ESTree::UnaryExpressionNode *U) { auto kind = UnaryOperatorInst::parseOperator(U->_operator->str()); // Handle the delete unary expression. https://es5.github.io/#x11.4.1 if (kind == UnaryOperatorInst::OpKind::DeleteKind) { if (auto *memberExpr = llvh::dyn_cast<ESTree::MemberExpressionNode>(U->_argument)) { LLVM_DEBUG(dbgs() << "IRGen delete member expression.\n"); return genMemberExpression(memberExpr, MemberExpressionOperation::Delete) .result; } if (auto *memberExpr = llvh::dyn_cast<ESTree::OptionalMemberExpressionNode>( U->_argument)) { LLVM_DEBUG(dbgs() << "IRGen delete optional member expression.\n"); return genOptionalMemberExpression( memberExpr, nullptr, MemberExpressionOperation::Delete) .result; } // Check for "delete identifier". Note that deleting unqualified identifiers // is prohibited in strict mode, so that case is handled earlier in the // semantic validator. Here we are left to handle the non-strict mode case. if (auto *iden = llvh::dyn_cast<ESTree::IdentifierNode>(U->_argument)) { assert( !curFunction()->function->isStrictMode() && "delete identifier encountered in strict mode"); // Check if this is a known variable. Identifier name = getNameFieldFromID(iden); auto *var = nameTable_.lookup(name); if (!var || llvh::isa<GlobalObjectProperty>(var)) { // If the variable doesn't exist or if it is global, we must generate // a delete global property instruction. return Builder.createDeletePropertyInst( Builder.getGlobalObject(), Builder.getLiteralString(name)); } else { // Otherwise it is a local variable which can't be deleted and we just // return false. return Builder.getLiteralBool(false); } } // Generate the code for the delete operand. genExpression(U->_argument); // Deleting any value or a result of an expression returns True. return Builder.getLiteralBool(true); } // Need to handle the special case of "typeof <undefined variable>". if (kind == UnaryOperatorInst::OpKind::TypeofKind) { if (auto *id = llvh::dyn_cast<ESTree::IdentifierNode>(U->_argument)) { Value *argument = genIdentifierExpression(id, true); return Builder.createUnaryOperatorInst(argument, kind); } } // Generate the unary operand: Value *argument = genExpression(U->_argument); auto *cookie = instrumentIR_.preUnaryExpression(U, argument); Value *result; if (kind == UnaryOperatorInst::OpKind::PlusKind) result = Builder.createAsNumberInst(argument); else result = Builder.createUnaryOperatorInst(argument, kind); return instrumentIR_.postUnaryExpression(U, cookie, result, argument); } Value *ESTreeIRGen::genUpdateExpr(ESTree::UpdateExpressionNode *updateExpr) { LLVM_DEBUG(dbgs() << "IRGen update expression.\n"); bool isPrefix = updateExpr->_prefix; UnaryOperatorInst::OpKind opKind; if (updateExpr->_operator->str() == "++") { opKind = UnaryOperatorInst::OpKind::IncKind; } else if (updateExpr->_operator->str() == "--") { opKind = UnaryOperatorInst::OpKind::DecKind; } else { llvm_unreachable("Invalid update operator"); } LReference lref = createLRef(updateExpr->_argument, false); // Load the original value. Value *original = Builder.createAsNumberInst(lref.emitLoad()); // Create the inc or dec. Value *result = Builder.createUnaryOperatorInst(original, opKind); // Store the result. lref.emitStore(result); // Depending on the prefixness return the previous value or the modified // value. return (isPrefix ? result : original); } /// Extract a name hint from a LReference. static Identifier extractNameHint(const LReference &lref) { Identifier nameHint{}; if (auto *var = lref.castAsVariable()) { nameHint = var->getName(); } else if (auto *globProp = lref.castAsGlobalObjectProperty()) { nameHint = globProp->getName()->getValue(); } return nameHint; } Value *ESTreeIRGen::genAssignmentExpr(ESTree::AssignmentExpressionNode *AE) { LLVM_DEBUG(dbgs() << "IRGen assignment operator.\n"); auto opStr = AE->_operator->str(); // Handle nested normal assignments non-recursively. if (opStr == "=") { auto list = ESTree::linearizeRight(AE, {"="}); // Create an LReference for every assignment left side. llvh::SmallVector<LReference, 1> lrefs; lrefs.reserve(list.size()); for (auto *e : list) { lrefs.push_back(createLRef(e->_left, false)); } Value *RHS = nullptr; auto lrefIterator = lrefs.end(); for (auto *e : llvh::make_range(list.rbegin(), list.rend())) { --lrefIterator; if (!RHS) RHS = genExpression(e->_right, extractNameHint(*lrefIterator)); Builder.setLocation(e->getDebugLoc()); auto *cookie = instrumentIR_.preAssignment(e, nullptr, RHS); RHS = instrumentIR_.postAssignment(e, cookie, RHS, nullptr, RHS); lrefIterator->emitStore(RHS); } return RHS; } auto AssignmentKind = BinaryOperatorInst::parseAssignmentOperator(opStr); LReference lref = createLRef(AE->_left, false); Identifier nameHint = extractNameHint(lref); Value *result; if (AssignmentKind == BinaryOperatorInst::OpKind::AssignShortCircuitOrKind || AssignmentKind == BinaryOperatorInst::OpKind::AssignShortCircuitAndKind || AssignmentKind == BinaryOperatorInst::OpKind::AssignNullishCoalesceKind) { return genLogicalAssignmentExpr(AE, AssignmentKind, lref, nameHint); } assert(AssignmentKind != BinaryOperatorInst::OpKind::IdentityKind); // Section 11.13.1 specifies that we should first load the // LHS before materializing the RHS. Unlike in C, this // code is well defined: "x+= x++". // https://es5.github.io/#x11.13.1 auto V = lref.emitLoad(); auto *RHS = genExpression(AE->_right, nameHint); auto *cookie = instrumentIR_.preAssignment(AE, V, RHS); result = Builder.createBinaryOperatorInst(V, RHS, AssignmentKind); result = instrumentIR_.postAssignment(AE, cookie, result, V, RHS); lref.emitStore(result); // Return the value that we stored as the result of the expression. return result; } Value *ESTreeIRGen::genLogicalAssignmentExpr( ESTree::AssignmentExpressionNode *AE, BinaryOperatorInst::OpKind AssignmentKind, LReference lref, Identifier nameHint) { // Logical assignment expressions must use short-circuiting logic. // BB which actually performs the assignment. BasicBlock *assignBB = Builder.createBasicBlock(Builder.getFunction()); // BB which simply continues without performing the assignment. BasicBlock *continueBB = Builder.createBasicBlock(Builder.getFunction()); auto *lhs = lref.emitLoad(); PhiInst::ValueListType values; PhiInst::BasicBlockListType blocks; values.push_back(lhs); blocks.push_back(Builder.getInsertionBlock()); switch (AssignmentKind) { case BinaryOperatorInst::OpKind::AssignShortCircuitOrKind: Builder.createCondBranchInst(lhs, continueBB, assignBB); break; case BinaryOperatorInst::OpKind::AssignShortCircuitAndKind: Builder.createCondBranchInst(lhs, assignBB, continueBB); break; case BinaryOperatorInst::OpKind::AssignNullishCoalesceKind: Builder.createCondBranchInst( Builder.createBinaryOperatorInst( lhs, Builder.getLiteralNull(), BinaryOperatorInst::OpKind::EqualKind), assignBB, continueBB); break; default: llvm_unreachable("invalid AssignmentKind in this branch"); } Builder.setInsertionBlock(assignBB); auto *rhs = genExpression(AE->_right, nameHint); auto *cookie = instrumentIR_.preAssignment(AE, lhs, rhs); auto *result = instrumentIR_.postAssignment(AE, cookie, rhs, lhs, rhs); lref.emitStore(result); values.push_back(result); blocks.push_back(Builder.getInsertionBlock()); Builder.createBranchInst(continueBB); Builder.setInsertionBlock(continueBB); // Final result is either the original value or the value assigned, // depending on which branch was taken. return Builder.createPhiInst(std::move(values), std::move(blocks)); } Value *ESTreeIRGen::genConditionalExpr(ESTree::ConditionalExpressionNode *C) { auto parentFunc = Builder.getInsertionBlock()->getParent(); PhiInst::ValueListType values; PhiInst::BasicBlockListType blocks; auto alternateBlock = Builder.createBasicBlock(parentFunc); auto consequentBlock = Builder.createBasicBlock(parentFunc); auto continueBlock = Builder.createBasicBlock(parentFunc); // Implement the ternary operator using control flow. We must use control // flow because the expressions may have side effects. genExpressionBranch(C->_test, consequentBlock, alternateBlock, nullptr); // The 'then' side: Builder.setInsertionBlock(consequentBlock); values.push_back(genExpression(C->_consequent)); blocks.push_back(Builder.getInsertionBlock()); Builder.createBranchInst(continueBlock); // The 'else' side: Builder.setInsertionBlock(alternateBlock); values.push_back(genExpression(C->_alternate)); blocks.push_back(Builder.getInsertionBlock()); Builder.createBranchInst(continueBlock); // Continue: Builder.setInsertionBlock(continueBlock); return Builder.createPhiInst(values, blocks); } Value *ESTreeIRGen::genIdentifierExpression( ESTree::IdentifierNode *Iden, bool afterTypeOf) { LLVM_DEBUG(dbgs() << "Looking for identifier \"" << Iden->_name << "\"\n"); // 'arguments' is an array-like object holding all function arguments. // If one of the parameters is called "arguments" then it shadows the // arguments keyword. if (Iden->_name->str() == "arguments" && !nameTable_.count(getNameFieldFromID(Iden))) { // If it is captured, we must use the captured value. if (curFunction()->capturedArguments) { return Builder.createLoadFrameInst(curFunction()->capturedArguments); } return curFunction()->createArgumentsInst; } // Lookup variable name. auto StrName = getNameFieldFromID(Iden); auto *Var = ensureVariableExists(Iden); // For uses of undefined as the global property, we make an optimization // to always return undefined constant. if (llvh::isa<GlobalObjectProperty>(Var) && StrName.str() == "undefined") { return Builder.getLiteralUndefined(); } LLVM_DEBUG( dbgs() << "Found variable " << StrName << " in function \"" << (llvh::isa<GlobalObjectProperty>(Var) ? StringRef("global") : cast<Variable>(Var) ->getParent() ->getFunction() ->getInternalNameStr()) << "\"\n"); // Typeof <variable> does not throw. return emitLoad(Builder, Var, afterTypeOf); } Value *ESTreeIRGen::genMetaProperty(ESTree::MetaPropertyNode *MP) { // Recognize "new.target" if (cast<ESTree::IdentifierNode>(MP->_meta)->_name->str() == "new") { if (cast<ESTree::IdentifierNode>(MP->_property)->_name->str() == "target") { Value *value; if (curFunction()->function->getDefinitionKind() == Function::DefinitionKind::ES6Arrow || curFunction()->function->getDefinitionKind() == Function::DefinitionKind::ES6Method) { value = curFunction()->capturedNewTarget; } else { value = Builder.createGetNewTargetInst(); } // If it is a variable, we must issue a load. if (auto *V = llvh::dyn_cast<Variable>(value)) return Builder.createLoadFrameInst(V); return value; } } llvm_unreachable("invalid MetaProperty"); } Value *ESTreeIRGen::genNewExpr(ESTree::NewExpressionNode *N) { LLVM_DEBUG(dbgs() << "IRGen 'new' statement/expression.\n"); Value *callee = genExpression(N->_callee); bool hasSpread = false; for (auto &arg : N->_arguments) { if (llvh::isa<ESTree::SpreadElementNode>(&arg)) { hasSpread = true; } } if (!hasSpread) { ConstructInst::ArgumentList args; for (auto &arg : N->_arguments) { args.push_back(genExpression(&arg)); } return Builder.createConstructInst(callee, args); } // Otherwise, there exists a spread argument, so the number of arguments // is variable. // Generate IR for this by creating an array and populating it with the // arguments, then calling HermesInternal.apply. auto *args = genArrayFromElements(N->_arguments); return genBuiltinCall(BuiltinMethod::HermesBuiltin_apply, {callee, args}); } Value *ESTreeIRGen::genLogicalExpression( ESTree::LogicalExpressionNode *logical) { auto opStr = logical->_operator->str(); LLVM_DEBUG(dbgs() << "IRGen of short circuiting: " << opStr << ".\n"); enum class Kind { And, // && Or, // || Coalesce, // ?? }; Kind kind; if (opStr == "&&") { kind = Kind::And; } else if (opStr == "||") { kind = Kind::Or; } else if (opStr == "??") { kind = Kind::Coalesce; } else { llvm_unreachable("Invalid update operator"); } // Generate a new temporary stack allocation. auto tempVarName = genAnonymousLabelName("logical"); auto parentFunc = Builder.getInsertionBlock()->getParent(); auto tempVar = Builder.createAllocStackInst(tempVarName); auto evalRHSBlock = Builder.createBasicBlock(parentFunc); auto continueBlock = Builder.createBasicBlock(parentFunc); auto LHS = genExpression(logical->_left); // Store the LHS value of the expression in preparation for the case where we // won't need to evaluate the RHS side of the expression. In that case, we // jump to continueBlock, which returns tempVar. Builder.createStoreStackInst(LHS, tempVar); // Notice that instead of negating the condition we swap the operands of the // branch. switch (kind) { case Kind::And: // Evaluate RHS only when the LHS is true. Builder.createCondBranchInst(LHS, evalRHSBlock, continueBlock); break; case Kind::Or: // Evaluate RHS only when the LHS is false. Builder.createCondBranchInst(LHS, continueBlock, evalRHSBlock); break; case Kind::Coalesce: // Evaluate RHS only if the value is undefined or null. // Use == instead of === to account for both values at once. Builder.createCondBranchInst( Builder.createBinaryOperatorInst( LHS, Builder.getLiteralNull(), BinaryOperatorInst::OpKind::EqualKind), evalRHSBlock, continueBlock); break; } // Continue the evaluation of the right-hand-side of the expression. Builder.setInsertionBlock(evalRHSBlock); auto RHS = genExpression(logical->_right); // Evaluate the RHS and store the result into the temporary variable. Builder.createStoreStackInst(RHS, tempVar); // Finally, jump to the continuation block. Builder.createBranchInst(continueBlock); // Load the content of the temp variable that was set in one of the branches. Builder.setInsertionBlock(continueBlock); return Builder.createLoadStackInst(tempVar); } void ESTreeIRGen::genLogicalExpressionBranch( ESTree::LogicalExpressionNode *logical, BasicBlock *onTrue, BasicBlock *onFalse, BasicBlock *onNullish) { auto opStr = logical->_operator->str(); LLVM_DEBUG(dbgs() << "IRGen of short circuiting: " << opStr << " branch.\n"); auto parentFunc = Builder.getInsertionBlock()->getParent(); auto *block = Builder.createBasicBlock(parentFunc); if (opStr == "&&") { genExpressionBranch(logical->_left, block, onFalse, onNullish); } else if (opStr == "||") { genExpressionBranch(logical->_left, onTrue, block, onNullish); } else { assert(opStr == "??" && "invalid logical operator"); genExpressionBranch(logical->_left, onTrue, onFalse, block); } Builder.setInsertionBlock(block); genExpressionBranch(logical->_right, onTrue, onFalse, onNullish); } Value *ESTreeIRGen::genTemplateLiteralExpr(ESTree::TemplateLiteralNode *Expr) { LLVM_DEBUG(dbgs() << "IRGen 'TemplateLiteral' expression.\n"); assert( Expr->_quasis.size() == Expr->_expressions.size() + 1 && "The string count should always be one more than substitution count."); // Construct an argument list for calling HermesInternal.concat(): // cookedStr0, substitution0, cookedStr1, ..., substitutionN, cookedStrN + 1, // skipping any empty string, except for the first cooked string, which is // going to be the `this` to the concat call. // Get the first cooked string. auto strItr = Expr->_quasis.begin(); auto *tempEltNode = cast<ESTree::TemplateElementNode>(&*strItr); auto *firstCookedStr = Builder.getLiteralString(tempEltNode->_cooked->str()); ++strItr; // If the template literal is effectively only one string, directly return it. if (strItr == Expr->_quasis.end()) { return firstCookedStr; } CallInst::ArgumentList argList; auto exprItr = Expr->_expressions.begin(); while (strItr != Expr->_quasis.end()) { auto *sub = genExpression(&*exprItr); argList.push_back(sub); tempEltNode = cast<ESTree::TemplateElementNode>(&*strItr); auto cookedStr = tempEltNode->_cooked->str(); if (!cookedStr.empty()) { argList.push_back(Builder.getLiteralString(cookedStr)); } ++strItr; ++exprItr; } assert( exprItr == Expr->_expressions.end() && "All the substitutions must have been collected."); // Generate a function call to HermesInternal.concat() with these arguments. return genHermesInternalCall("concat", firstCookedStr, argList); } Value *ESTreeIRGen::genTaggedTemplateExpr( ESTree::TaggedTemplateExpressionNode *Expr) { LLVM_DEBUG(dbgs() << "IRGen 'TaggedTemplateExpression' expression.\n"); // Step 1: get the template object. auto *templateLit = cast<ESTree::TemplateLiteralNode>(Expr->_quasi); // Construct an argument list for calling HermesInternal.getTemplateObject(): // [template object ID, dup, raw strings, (optional) cooked strings]. CallInst::ArgumentList argList; // Retrieve template object ID. Module::RawStringList rawStrings; for (auto &n : templateLit->_quasis) { auto element = cast<ESTree::TemplateElementNode>(&n); rawStrings.push_back(Builder.getLiteralString(element->_raw->str())); } uint32_t templateObjID = Mod->getTemplateObjectID(std::move(rawStrings)); argList.push_back(Builder.getLiteralNumber(templateObjID)); // dup is true if the cooked strings and raw strings are duplicated. bool dup = true; // Add the argument dup first as a placeholder which we overwrite with // the correct value later. argList.push_back(Builder.getLiteralBool(dup)); for (auto &node : templateLit->_quasis) { auto *templateElt = cast<ESTree::TemplateElementNode>(&node); if (templateElt->_cooked != templateElt->_raw) { dup = false; } argList.push_back(Builder.getLiteralString(templateElt->_raw->str())); } argList[1] = Builder.getLiteralBool(dup); // If the cooked strings are not the same as raw strings, append them to // argument list. if (!dup) { for (auto &node : templateLit->_quasis) { auto *templateElt = cast<ESTree::TemplateElementNode>(&node); if (templateElt->_cooked) { argList.push_back( Builder.getLiteralString(templateElt->_cooked->str())); } else { argList.push_back(Builder.getLiteralUndefined()); } } } // Generate a function call to HermesInternal.getTemplateObject() with these // arguments. auto *templateObj = genBuiltinCall(BuiltinMethod::HermesBuiltin_getTemplateObject, argList); // Step 2: call the tag function, passing the template object followed by a // list of substitutions as arguments. CallInst::ArgumentList tagFuncArgList; tagFuncArgList.push_back(templateObj); for (auto &sub : templateLit->_expressions) { tagFuncArgList.push_back(genExpression(&sub)); } Value *callee; Value *thisVal; // Tag function is a member expression. if (auto *Mem = llvh::dyn_cast<ESTree::MemberExpressionNode>(Expr->_tag)) { Value *obj = genExpression(Mem->_object); Value *prop = genMemberExpressionProperty(Mem); // Call the callee with obj as the 'this'. thisVal = obj; callee = Builder.createLoadPropertyInst(obj, prop); } else { thisVal = Builder.getLiteralUndefined(); callee = genExpression(Expr->_tag); } return Builder.createCallInst(callee, thisVal, tagFuncArgList); } } // namespace irgen } // namespace hermes