| /* |
| * Copyright (C) 2011-2020 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "DFGByteCodeParser.h" |
| |
| #if ENABLE(DFG_JIT) |
| |
| #include "ArithProfile.h" |
| #include "ArrayConstructor.h" |
| #include "BasicBlockLocation.h" |
| #include "BuiltinNames.h" |
| #include "BytecodeGenerator.h" |
| #include "BytecodeUseDef.h" |
| #include "CacheableIdentifierInlines.h" |
| #include "CallLinkStatus.h" |
| #include "CodeBlock.h" |
| #include "CodeBlockWithJITType.h" |
| #include "CommonSlowPaths.h" |
| #include "DFGAbstractHeap.h" |
| #include "DFGArrayMode.h" |
| #include "DFGCFG.h" |
| #include "DFGCapabilities.h" |
| #include "DFGClobberize.h" |
| #include "DFGClobbersExitState.h" |
| #include "DFGGraph.h" |
| #include "DFGJITCode.h" |
| #include "FunctionCodeBlock.h" |
| #include "GetByStatus.h" |
| #include "GetterSetter.h" |
| #include "Heap.h" |
| #include "InByIdStatus.h" |
| #include "InstanceOfStatus.h" |
| #include "JSArrayIterator.h" |
| #include "JSCInlines.h" |
| #include "JSImmutableButterfly.h" |
| #include "JSInternalPromise.h" |
| #include "JSInternalPromiseConstructor.h" |
| #include "JSModuleEnvironment.h" |
| #include "JSModuleNamespaceObject.h" |
| #include "JSPromiseConstructor.h" |
| #include "NumberConstructor.h" |
| #include "ObjectConstructor.h" |
| #include "OpcodeInlines.h" |
| #include "PreciseJumpTargets.h" |
| #include "PutByIdFlags.h" |
| #include "PutByIdStatus.h" |
| #include "RegExpPrototype.h" |
| #include "StackAlignment.h" |
| #include "StringConstructor.h" |
| #include "StructureStubInfo.h" |
| #include "SymbolConstructor.h" |
| #include "Watchdog.h" |
| #include <wtf/CommaPrinter.h> |
| #include <wtf/HashMap.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/Scope.h> |
| #include <wtf/SetForScope.h> |
| #include <wtf/StdLibExtras.h> |
| |
| namespace JSC { namespace DFG { |
| |
| namespace DFGByteCodeParserInternal { |
| #ifdef NDEBUG |
| static constexpr bool verbose = false; |
| #else |
| static constexpr bool verbose = true; |
| #endif |
| } // namespace DFGByteCodeParserInternal |
| |
| #define VERBOSE_LOG(...) do { \ |
| if (DFGByteCodeParserInternal::verbose && Options::verboseDFGBytecodeParsing()) \ |
| dataLog(__VA_ARGS__); \ |
| } while (false) |
| |
| // === ByteCodeParser === |
| // |
| // This class is used to compile the dataflow graph from a CodeBlock. |
| class ByteCodeParser { |
| public: |
| ByteCodeParser(Graph& graph) |
| : m_vm(&graph.m_vm) |
| , m_codeBlock(graph.m_codeBlock) |
| , m_profiledBlock(graph.m_profiledBlock) |
| , m_graph(graph) |
| , m_currentBlock(0) |
| , m_currentIndex(0) |
| , m_constantUndefined(graph.freeze(jsUndefined())) |
| , m_constantNull(graph.freeze(jsNull())) |
| , m_constantNaN(graph.freeze(jsNumber(PNaN))) |
| , m_constantOne(graph.freeze(jsNumber(1))) |
| , m_numArguments(m_codeBlock->numParameters()) |
| , m_numLocals(m_codeBlock->numCalleeLocals()) |
| , m_numTmps(m_codeBlock->numTmps()) |
| , m_parameterSlots(0) |
| , m_numPassedVarArgs(0) |
| , m_inlineStackTop(0) |
| , m_currentInstruction(0) |
| , m_hasDebuggerEnabled(graph.hasDebuggerEnabled()) |
| { |
| ASSERT(m_profiledBlock); |
| } |
| |
| // Parse a full CodeBlock of bytecode. |
| void parse(); |
| |
| private: |
| struct InlineStackEntry; |
| |
| // Just parse from m_currentIndex to the end of the current CodeBlock. |
| void parseCodeBlock(); |
| |
| void ensureLocals(unsigned newNumLocals) |
| { |
| VERBOSE_LOG(" ensureLocals: trying to raise m_numLocals from ", m_numLocals, " to ", newNumLocals, "\n"); |
| if (newNumLocals <= m_numLocals) |
| return; |
| m_numLocals = newNumLocals; |
| for (size_t i = 0; i < m_graph.numBlocks(); ++i) |
| m_graph.block(i)->ensureLocals(newNumLocals); |
| } |
| |
| void ensureTmps(unsigned newNumTmps) |
| { |
| VERBOSE_LOG(" ensureTmps: trying to raise m_numTmps from ", m_numTmps, " to ", newNumTmps, "\n"); |
| if (newNumTmps <= m_numTmps) |
| return; |
| m_numTmps = newNumTmps; |
| for (size_t i = 0; i < m_graph.numBlocks(); ++i) |
| m_graph.block(i)->ensureTmps(newNumTmps); |
| } |
| |
| |
| // Helper for min and max. |
| template<typename ChecksFunctor> |
| bool handleMinMax(VirtualRegister result, NodeType op, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& insertChecks); |
| |
| void refineStatically(CallLinkStatus&, Node* callTarget); |
| // Blocks can either be targetable (i.e. in the m_blockLinkingTargets of one InlineStackEntry) with a well-defined bytecodeBegin, |
| // or they can be untargetable, with bytecodeBegin==UINT_MAX, to be managed manually and not by the linkBlock machinery. |
| // This is used most notably when doing polyvariant inlining (it requires a fair bit of control-flow with no bytecode analog). |
| // It is also used when doing an early return from an inlined callee: it is easier to fix the bytecode index later on if needed |
| // than to move the right index all the way to the treatment of op_ret. |
| BasicBlock* allocateTargetableBlock(BytecodeIndex); |
| BasicBlock* allocateUntargetableBlock(); |
| // An untargetable block can be given a bytecodeIndex to be later managed by linkBlock, but only once, and it can never go in the other direction |
| void makeBlockTargetable(BasicBlock*, BytecodeIndex); |
| void addJumpTo(BasicBlock*); |
| void addJumpTo(unsigned bytecodeIndex); |
| // Handle calls. This resolves issues surrounding inlining and intrinsics. |
| enum Terminality { Terminal, NonTerminal }; |
| Terminality handleCall( |
| VirtualRegister result, NodeType op, InlineCallFrame::Kind, unsigned instructionSize, |
| Node* callTarget, int argumentCountIncludingThis, int registerOffset, CallLinkStatus, |
| SpeculatedType prediction); |
| template<typename CallOp> |
| Terminality handleCall(const Instruction* pc, NodeType op, CallMode); |
| template<typename CallOp> |
| Terminality handleVarargsCall(const Instruction* pc, NodeType op, CallMode); |
| void emitFunctionChecks(CallVariant, Node* callTarget, VirtualRegister thisArgumnt); |
| void emitArgumentPhantoms(int registerOffset, int argumentCountIncludingThis); |
| Node* getArgumentCount(); |
| template<typename ChecksFunctor> |
| bool handleRecursiveTailCall(Node* callTargetNode, CallVariant, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& emitFunctionCheckIfNeeded); |
| unsigned inliningCost(CallVariant, int argumentCountIncludingThis, InlineCallFrame::Kind); // Return UINT_MAX if it's not an inlining candidate. By convention, intrinsics have a cost of 1. |
| // Handle inlining. Return true if it succeeded, false if we need to plant a call. |
| bool handleVarargsInlining(Node* callTargetNode, VirtualRegister result, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, VirtualRegister argumentsArgument, unsigned argumentsOffset, NodeType callOp, InlineCallFrame::Kind); |
| unsigned getInliningBalance(const CallLinkStatus&, CodeSpecializationKind); |
| enum class CallOptimizationResult { OptimizedToJump, Inlined, DidNothing }; |
| CallOptimizationResult handleCallVariant(Node* callTargetNode, VirtualRegister result, CallVariant, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, BytecodeIndex nextIndex, InlineCallFrame::Kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, bool needsToCheckCallee); |
| CallOptimizationResult handleInlining(Node* callTargetNode, VirtualRegister result, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, BytecodeIndex nextIndex, NodeType callOp, InlineCallFrame::Kind, SpeculatedType prediction); |
| template<typename ChecksFunctor> |
| void inlineCall(Node* callTargetNode, VirtualRegister result, CallVariant, int registerOffset, int argumentCountIncludingThis, InlineCallFrame::Kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks); |
| // Handle intrinsic functions. Return true if it succeeded, false if we need to plant a call. |
| template<typename ChecksFunctor> |
| bool handleIntrinsicCall(Node* callee, VirtualRegister result, Intrinsic, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, const ChecksFunctor& insertChecks); |
| template<typename ChecksFunctor> |
| bool handleDOMJITCall(Node* callee, VirtualRegister result, const DOMJIT::Signature*, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, const ChecksFunctor& insertChecks); |
| template<typename ChecksFunctor> |
| bool handleIntrinsicGetter(VirtualRegister result, SpeculatedType prediction, const GetByIdVariant& intrinsicVariant, Node* thisNode, const ChecksFunctor& insertChecks); |
| template<typename ChecksFunctor> |
| bool handleTypedArrayConstructor(VirtualRegister result, InternalFunction*, int registerOffset, int argumentCountIncludingThis, TypedArrayType, const ChecksFunctor& insertChecks); |
| template<typename ChecksFunctor> |
| bool handleConstantInternalFunction(Node* callTargetNode, VirtualRegister result, InternalFunction*, int registerOffset, int argumentCountIncludingThis, CodeSpecializationKind, SpeculatedType, const ChecksFunctor& insertChecks); |
| Node* handlePutByOffset(Node* base, unsigned identifier, PropertyOffset, Node* value); |
| Node* handleGetByOffset(SpeculatedType, Node* base, unsigned identifierNumber, PropertyOffset, NodeType = GetByOffset); |
| bool handleDOMJITGetter(VirtualRegister result, const GetByIdVariant&, Node* thisNode, unsigned identifierNumber, SpeculatedType prediction); |
| bool handleModuleNamespaceLoad(VirtualRegister result, SpeculatedType, Node* base, GetByStatus); |
| |
| template<typename Bytecode> |
| void handlePutByVal(Bytecode, unsigned instructionSize); |
| template <typename Bytecode> |
| void handlePutAccessorById(NodeType, Bytecode); |
| template <typename Bytecode> |
| void handlePutAccessorByVal(NodeType, Bytecode); |
| template <typename Bytecode> |
| void handleNewFunc(NodeType, Bytecode); |
| template <typename Bytecode> |
| void handleNewFuncExp(NodeType, Bytecode); |
| template <typename Bytecode> |
| void handleCreateInternalFieldObject(const ClassInfo*, NodeType createOp, NodeType newOp, Bytecode); |
| |
| // Create a presence ObjectPropertyCondition based on some known offset and structure set. Does not |
| // check the validity of the condition, but it may return a null one if it encounters a contradiction. |
| ObjectPropertyCondition presenceLike( |
| JSObject* knownBase, UniquedStringImpl*, PropertyOffset, const StructureSet&); |
| |
| // Attempt to watch the presence of a property. It will watch that the property is present in the same |
| // way as in all of the structures in the set. It may emit code instead of just setting a watchpoint. |
| // Returns true if this all works out. |
| bool checkPresenceLike(JSObject* knownBase, UniquedStringImpl*, PropertyOffset, const StructureSet&); |
| void checkPresenceLike(Node* base, UniquedStringImpl*, PropertyOffset, const StructureSet&); |
| |
| // Works with both GetByIdVariant and the setter form of PutByIdVariant. |
| template<typename VariantType> |
| Node* load(SpeculatedType, Node* base, unsigned identifierNumber, const VariantType&); |
| |
| Node* store(Node* base, unsigned identifier, const PutByIdVariant&, Node* value); |
| |
| template<typename Op> |
| void parseGetById(const Instruction*); |
| void handleGetById( |
| VirtualRegister destination, SpeculatedType, Node* base, unsigned identifierNumber, GetByStatus, AccessType, unsigned instructionSize); |
| void emitPutById( |
| Node* base, unsigned identifierNumber, Node* value, const PutByIdStatus&, bool isDirect); |
| void handlePutById( |
| Node* base, unsigned identifierNumber, Node* value, const PutByIdStatus&, |
| bool isDirect, unsigned intructionSize); |
| |
| // Either register a watchpoint or emit a check for this condition. Returns false if the |
| // condition no longer holds, and therefore no reasonable check can be emitted. |
| bool check(const ObjectPropertyCondition&); |
| |
| GetByOffsetMethod promoteToConstant(GetByOffsetMethod); |
| |
| // Either register a watchpoint or emit a check for this condition. It must be a Presence |
| // condition. It will attempt to promote a Presence condition to an Equivalence condition. |
| // Emits code for the loaded value that the condition guards, and returns a node containing |
| // the loaded value. Returns null if the condition no longer holds. |
| GetByOffsetMethod planLoad(const ObjectPropertyCondition&); |
| Node* load(SpeculatedType, unsigned identifierNumber, const GetByOffsetMethod&, NodeType = GetByOffset); |
| Node* load(SpeculatedType, const ObjectPropertyCondition&, NodeType = GetByOffset); |
| |
| // Calls check() for each condition in the set: that is, it either emits checks or registers |
| // watchpoints (or a combination of the two) to make the conditions hold. If any of those |
| // conditions are no longer checkable, returns false. |
| bool check(const ObjectPropertyConditionSet&); |
| |
| // Calls check() for those conditions that aren't the slot base, and calls load() for the slot |
| // base. Does a combination of watchpoint registration and check emission to guard the |
| // conditions, and emits code to load the value from the slot base. Returns a node containing |
| // the loaded value. Returns null if any of the conditions were no longer checkable. |
| GetByOffsetMethod planLoad(const ObjectPropertyConditionSet&); |
| Node* load(SpeculatedType, const ObjectPropertyConditionSet&, NodeType = GetByOffset); |
| |
| void prepareToParseBlock(); |
| void clearCaches(); |
| |
| // Parse a single basic block of bytecode instructions. |
| void parseBlock(unsigned limit); |
| // Link block successors. |
| void linkBlock(BasicBlock*, Vector<BasicBlock*>& possibleTargets); |
| void linkBlocks(Vector<BasicBlock*>& unlinkedBlocks, Vector<BasicBlock*>& possibleTargets); |
| |
| void progressToNextCheckpoint() |
| { |
| m_currentIndex = BytecodeIndex(m_currentIndex.offset(), m_currentIndex.checkpoint() + 1); |
| // At this point, it's again OK to OSR exit. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| processSetLocalQueue(); |
| } |
| |
| VariableAccessData* newVariableAccessData(Operand operand) |
| { |
| ASSERT(!operand.isConstant()); |
| |
| m_graph.m_variableAccessData.append(operand); |
| return &m_graph.m_variableAccessData.last(); |
| } |
| |
| // Get/Set the operands/result of a bytecode instruction. |
| Node* getDirect(Operand operand) |
| { |
| ASSERT(!operand.isConstant()); |
| |
| if (operand.isArgument()) |
| return getArgument(operand.virtualRegister()); |
| |
| return getLocalOrTmp(operand); |
| } |
| |
| Node* get(VirtualRegister operand) |
| { |
| if (operand.isConstant()) { |
| unsigned constantIndex = operand.toConstantIndex(); |
| unsigned oldSize = m_constants.size(); |
| if (constantIndex >= oldSize || !m_constants[constantIndex]) { |
| const CodeBlock& codeBlock = *m_inlineStackTop->m_codeBlock; |
| JSValue value = codeBlock.getConstant(operand); |
| SourceCodeRepresentation sourceCodeRepresentation = codeBlock.constantSourceCodeRepresentation(operand); |
| if (constantIndex >= oldSize) { |
| m_constants.grow(constantIndex + 1); |
| for (unsigned i = oldSize; i < m_constants.size(); ++i) |
| m_constants[i] = nullptr; |
| } |
| |
| Node* constantNode = nullptr; |
| if (sourceCodeRepresentation == SourceCodeRepresentation::Double) |
| constantNode = addToGraph(DoubleConstant, OpInfo(m_graph.freezeStrong(jsDoubleNumber(value.asNumber())))); |
| else |
| constantNode = addToGraph(JSConstant, OpInfo(m_graph.freezeStrong(value))); |
| m_constants[constantIndex] = constantNode; |
| } |
| ASSERT(m_constants[constantIndex]); |
| return m_constants[constantIndex]; |
| } |
| |
| if (inlineCallFrame()) { |
| if (!inlineCallFrame()->isClosureCall) { |
| JSFunction* callee = inlineCallFrame()->calleeConstant(); |
| if (operand.offset() == CallFrameSlot::callee) |
| return weakJSConstant(callee); |
| } |
| } else if (operand.offset() == CallFrameSlot::callee) { |
| // We have to do some constant-folding here because this enables CreateThis folding. Note |
| // that we don't have such watchpoint-based folding for inlined uses of Callee, since in that |
| // case if the function is a singleton then we already know it. |
| if (FunctionExecutable* executable = jsDynamicCast<FunctionExecutable*>(*m_vm, m_codeBlock->ownerExecutable())) { |
| if (JSFunction* function = executable->singleton().inferredValue()) { |
| m_graph.watchpoints().addLazily(executable); |
| return weakJSConstant(function); |
| } |
| } |
| return addToGraph(GetCallee); |
| } |
| |
| return getDirect(m_inlineStackTop->remapOperand(operand)); |
| } |
| |
| enum SetMode { |
| // A normal set which follows a two-phase commit that spans code origins. During |
| // the current code origin it issues a MovHint, and at the start of the next |
| // code origin there will be a SetLocal. If the local needs flushing, the second |
| // SetLocal will be preceded with a Flush. |
| NormalSet, |
| |
| // A set where the SetLocal happens immediately and there is still a Flush. This |
| // is relevant when assigning to a local in tricky situations for the delayed |
| // SetLocal logic but where we know that we have not performed any side effects |
| // within this code origin. This is a safe replacement for NormalSet anytime we |
| // know that we have not yet performed side effects in this code origin. |
| ImmediateSetWithFlush, |
| |
| // A set where the SetLocal happens immediately and we do not Flush it even if |
| // this is a local that is marked as needing it. This is relevant when |
| // initializing locals at the top of a function. |
| ImmediateNakedSet |
| }; |
| |
| Node* setDirect(Operand operand, Node* value, SetMode setMode = NormalSet) |
| { |
| addToGraph(MovHint, OpInfo(operand), value); |
| |
| // We can't exit anymore because our OSR exit state has changed. |
| m_exitOK = false; |
| |
| DelayedSetLocal delayed(currentCodeOrigin(), operand, value, setMode); |
| |
| if (setMode == NormalSet) { |
| m_setLocalQueue.append(delayed); |
| return nullptr; |
| } |
| |
| return delayed.execute(this); |
| } |
| |
| void processSetLocalQueue() |
| { |
| for (unsigned i = 0; i < m_setLocalQueue.size(); ++i) |
| m_setLocalQueue[i].execute(this); |
| m_setLocalQueue.shrink(0); |
| } |
| |
| Node* set(VirtualRegister operand, Node* value, SetMode setMode = NormalSet) |
| { |
| return setDirect(m_inlineStackTop->remapOperand(operand), value, setMode); |
| } |
| |
| Node* injectLazyOperandSpeculation(Node* node) |
| { |
| ASSERT(node->op() == GetLocal); |
| ASSERT(node->origin.semantic.bytecodeIndex() == m_currentIndex); |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| LazyOperandValueProfileKey key(m_currentIndex, node->operand()); |
| SpeculatedType prediction = m_inlineStackTop->m_lazyOperands.prediction(locker, key); |
| node->variableAccessData()->predict(prediction); |
| return node; |
| } |
| |
| // Used in implementing get/set, above, where the operand is a local variable. |
| Node* getLocalOrTmp(Operand operand) |
| { |
| ASSERT(operand.isTmp() || operand.isLocal()); |
| Node*& node = m_currentBlock->variablesAtTail.operand(operand); |
| |
| // This has two goals: 1) link together variable access datas, and 2) |
| // try to avoid creating redundant GetLocals. (1) is required for |
| // correctness - no other phase will ensure that block-local variable |
| // access data unification is done correctly. (2) is purely opportunistic |
| // and is meant as an compile-time optimization only. |
| |
| VariableAccessData* variable; |
| |
| if (node) { |
| variable = node->variableAccessData(); |
| |
| switch (node->op()) { |
| case GetLocal: |
| return node; |
| case SetLocal: |
| return node->child1().node(); |
| default: |
| break; |
| } |
| } else |
| variable = newVariableAccessData(operand); |
| |
| node = injectLazyOperandSpeculation(addToGraph(GetLocal, OpInfo(variable))); |
| return node; |
| } |
| Node* setLocalOrTmp(const CodeOrigin& semanticOrigin, Operand operand, Node* value, SetMode setMode = NormalSet) |
| { |
| ASSERT(operand.isTmp() || operand.isLocal()); |
| SetForScope<CodeOrigin> originChange(m_currentSemanticOrigin, semanticOrigin); |
| |
| if (operand.isTmp() && static_cast<unsigned>(operand.value()) >= m_numTmps) { |
| if (inlineCallFrame()) |
| dataLogLn(*inlineCallFrame()); |
| dataLogLn("Bad operand: ", operand, " but current number of tmps is: ", m_numTmps, " code block has: ", m_profiledBlock->numTmps(), " tmps."); |
| CRASH(); |
| } |
| |
| if (setMode != ImmediateNakedSet && !operand.isTmp()) { |
| VirtualRegister reg = operand.virtualRegister(); |
| ArgumentPosition* argumentPosition = findArgumentPositionForLocal(reg); |
| if (argumentPosition) |
| flushDirect(operand, argumentPosition); |
| else if (m_graph.needsScopeRegister() && reg == m_codeBlock->scopeRegister()) |
| flush(operand); |
| } |
| |
| VariableAccessData* variableAccessData = newVariableAccessData(operand); |
| variableAccessData->mergeStructureCheckHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(semanticOrigin.bytecodeIndex(), BadCache)); |
| variableAccessData->mergeCheckArrayHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(semanticOrigin.bytecodeIndex(), BadIndexingType)); |
| Node* node = addToGraph(SetLocal, OpInfo(variableAccessData), value); |
| m_currentBlock->variablesAtTail.operand(operand) = node; |
| return node; |
| } |
| |
| // Used in implementing get/set, above, where the operand is an argument. |
| Node* getArgument(VirtualRegister operand) |
| { |
| unsigned argument = operand.toArgument(); |
| ASSERT(argument < m_numArguments); |
| |
| Node* node = m_currentBlock->variablesAtTail.argument(argument); |
| |
| VariableAccessData* variable; |
| |
| if (node) { |
| variable = node->variableAccessData(); |
| |
| switch (node->op()) { |
| case GetLocal: |
| return node; |
| case SetLocal: |
| return node->child1().node(); |
| default: |
| break; |
| } |
| } else |
| variable = newVariableAccessData(operand); |
| |
| node = injectLazyOperandSpeculation(addToGraph(GetLocal, OpInfo(variable))); |
| m_currentBlock->variablesAtTail.argument(argument) = node; |
| return node; |
| } |
| Node* setArgument(const CodeOrigin& semanticOrigin, Operand operand, Node* value, SetMode setMode = NormalSet) |
| { |
| SetForScope<CodeOrigin> originChange(m_currentSemanticOrigin, semanticOrigin); |
| |
| VirtualRegister reg = operand.virtualRegister(); |
| unsigned argument = reg.toArgument(); |
| ASSERT(argument < m_numArguments); |
| |
| VariableAccessData* variableAccessData = newVariableAccessData(reg); |
| |
| // Always flush arguments, except for 'this'. If 'this' is created by us, |
| // then make sure that it's never unboxed. |
| if (argument || m_graph.needsFlushedThis()) { |
| if (setMode != ImmediateNakedSet) |
| flushDirect(reg); |
| } |
| |
| if (!argument && m_codeBlock->specializationKind() == CodeForConstruct) |
| variableAccessData->mergeShouldNeverUnbox(true); |
| |
| variableAccessData->mergeStructureCheckHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(semanticOrigin.bytecodeIndex(), BadCache)); |
| variableAccessData->mergeCheckArrayHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(semanticOrigin.bytecodeIndex(), BadIndexingType)); |
| Node* node = addToGraph(SetLocal, OpInfo(variableAccessData), value); |
| m_currentBlock->variablesAtTail.argument(argument) = node; |
| return node; |
| } |
| |
| ArgumentPosition* findArgumentPositionForArgument(int argument) |
| { |
| InlineStackEntry* stack = m_inlineStackTop; |
| while (stack->m_inlineCallFrame) |
| stack = stack->m_caller; |
| return stack->m_argumentPositions[argument]; |
| } |
| |
| ArgumentPosition* findArgumentPositionForLocal(VirtualRegister operand) |
| { |
| for (InlineStackEntry* stack = m_inlineStackTop; ; stack = stack->m_caller) { |
| InlineCallFrame* inlineCallFrame = stack->m_inlineCallFrame; |
| if (!inlineCallFrame) |
| break; |
| if (operand.offset() < static_cast<int>(inlineCallFrame->stackOffset + CallFrame::headerSizeInRegisters)) |
| continue; |
| if (operand.offset() >= static_cast<int>(inlineCallFrame->stackOffset + CallFrame::thisArgumentOffset() + inlineCallFrame->argumentsWithFixup.size())) |
| continue; |
| int argument = VirtualRegister(operand.offset() - inlineCallFrame->stackOffset).toArgument(); |
| return stack->m_argumentPositions[argument]; |
| } |
| return nullptr; |
| } |
| |
| ArgumentPosition* findArgumentPosition(Operand operand) |
| { |
| if (operand.isTmp()) |
| return nullptr; |
| if (operand.isArgument()) |
| return findArgumentPositionForArgument(operand.toArgument()); |
| return findArgumentPositionForLocal(operand.virtualRegister()); |
| } |
| |
| template<typename AddFlushDirectFunc> |
| void flushImpl(InlineCallFrame* inlineCallFrame, const AddFlushDirectFunc& addFlushDirect) |
| { |
| int numArguments; |
| if (inlineCallFrame) { |
| ASSERT(!m_graph.hasDebuggerEnabled()); |
| numArguments = inlineCallFrame->argumentsWithFixup.size(); |
| if (inlineCallFrame->isClosureCall) |
| addFlushDirect(inlineCallFrame, remapOperand(inlineCallFrame, CallFrameSlot::callee)); |
| if (inlineCallFrame->isVarargs()) |
| addFlushDirect(inlineCallFrame, remapOperand(inlineCallFrame, CallFrameSlot::argumentCountIncludingThis)); |
| } else |
| numArguments = m_graph.baselineCodeBlockFor(inlineCallFrame)->numParameters(); |
| |
| for (unsigned argument = numArguments; argument--;) |
| addFlushDirect(inlineCallFrame, remapOperand(inlineCallFrame, virtualRegisterForArgument(argument))); |
| |
| if (m_graph.needsScopeRegister()) |
| addFlushDirect(nullptr, m_graph.m_codeBlock->scopeRegister()); |
| } |
| |
| template<typename AddFlushDirectFunc, typename AddPhantomLocalDirectFunc> |
| void flushForTerminalImpl(CodeOrigin origin, const AddFlushDirectFunc& addFlushDirect, const AddPhantomLocalDirectFunc& addPhantomLocalDirect) |
| { |
| bool isCallerOrigin = false; |
| origin.walkUpInlineStack( |
| [&] (CodeOrigin origin) { |
| BytecodeIndex bytecodeIndex = origin.bytecodeIndex(); |
| InlineCallFrame* inlineCallFrame = origin.inlineCallFrame(); |
| flushImpl(inlineCallFrame, addFlushDirect); |
| |
| CodeBlock* codeBlock = m_graph.baselineCodeBlockFor(inlineCallFrame); |
| FullBytecodeLiveness& fullLiveness = m_graph.livenessFor(codeBlock); |
| // Note: We don't need to handle tmps here because tmps are not required to be flushed to the stack. |
| const auto& livenessAtBytecode = fullLiveness.getLiveness(bytecodeIndex, m_graph.appropriateLivenessCalculationPoint(origin, isCallerOrigin)); |
| for (unsigned local = codeBlock->numCalleeLocals(); local--;) { |
| if (livenessAtBytecode[local]) |
| addPhantomLocalDirect(inlineCallFrame, remapOperand(inlineCallFrame, virtualRegisterForLocal(local))); |
| } |
| isCallerOrigin = true; |
| }); |
| } |
| |
| void flush(Operand operand) |
| { |
| flushDirect(m_inlineStackTop->remapOperand(operand)); |
| } |
| |
| void flushDirect(Operand operand) |
| { |
| flushDirect(operand, findArgumentPosition(operand)); |
| } |
| |
| void flushDirect(Operand operand, ArgumentPosition* argumentPosition) |
| { |
| addFlushOrPhantomLocal<Flush>(operand, argumentPosition); |
| } |
| |
| template<NodeType nodeType> |
| void addFlushOrPhantomLocal(Operand operand, ArgumentPosition* argumentPosition) |
| { |
| ASSERT(!operand.isConstant()); |
| |
| Node*& node = m_currentBlock->variablesAtTail.operand(operand); |
| |
| VariableAccessData* variable; |
| |
| if (node) |
| variable = node->variableAccessData(); |
| else |
| variable = newVariableAccessData(operand); |
| |
| node = addToGraph(nodeType, OpInfo(variable)); |
| if (argumentPosition) |
| argumentPosition->addVariable(variable); |
| } |
| |
| void phantomLocalDirect(Operand operand) |
| { |
| addFlushOrPhantomLocal<PhantomLocal>(operand, findArgumentPosition(operand)); |
| } |
| |
| void flush(InlineStackEntry* inlineStackEntry) |
| { |
| auto addFlushDirect = [&] (InlineCallFrame*, Operand operand) { flushDirect(operand); }; |
| flushImpl(inlineStackEntry->m_inlineCallFrame, addFlushDirect); |
| } |
| |
| void flushForTerminal() |
| { |
| auto addFlushDirect = [&] (InlineCallFrame*, Operand operand) { flushDirect(operand); }; |
| auto addPhantomLocalDirect = [&] (InlineCallFrame*, Operand operand) { phantomLocalDirect(operand); }; |
| flushForTerminalImpl(currentCodeOrigin(), addFlushDirect, addPhantomLocalDirect); |
| } |
| |
| void flushForReturn() |
| { |
| flush(m_inlineStackTop); |
| } |
| |
| void flushIfTerminal(SwitchData& data) |
| { |
| if (data.fallThrough.bytecodeIndex() > m_currentIndex.offset()) |
| return; |
| |
| for (unsigned i = data.cases.size(); i--;) { |
| if (data.cases[i].target.bytecodeIndex() > m_currentIndex.offset()) |
| return; |
| } |
| |
| flushForTerminal(); |
| } |
| |
| // Assumes that the constant should be strongly marked. |
| Node* jsConstant(JSValue constantValue) |
| { |
| return addToGraph(JSConstant, OpInfo(m_graph.freezeStrong(constantValue))); |
| } |
| |
| Node* weakJSConstant(JSValue constantValue) |
| { |
| return addToGraph(JSConstant, OpInfo(m_graph.freeze(constantValue))); |
| } |
| |
| // Helper functions to get/set the this value. |
| Node* getThis() |
| { |
| return get(m_inlineStackTop->m_codeBlock->thisRegister()); |
| } |
| |
| void setThis(Node* value) |
| { |
| set(m_inlineStackTop->m_codeBlock->thisRegister(), value); |
| } |
| |
| InlineCallFrame* inlineCallFrame() |
| { |
| return m_inlineStackTop->m_inlineCallFrame; |
| } |
| |
| bool allInlineFramesAreTailCalls() |
| { |
| return !inlineCallFrame() || !inlineCallFrame()->getCallerSkippingTailCalls(); |
| } |
| |
| CodeOrigin currentCodeOrigin() |
| { |
| return CodeOrigin(m_currentIndex, inlineCallFrame()); |
| } |
| |
| NodeOrigin currentNodeOrigin() |
| { |
| CodeOrigin semantic; |
| CodeOrigin forExit; |
| |
| if (m_currentSemanticOrigin.isSet()) |
| semantic = m_currentSemanticOrigin; |
| else |
| semantic = currentCodeOrigin(); |
| |
| forExit = currentCodeOrigin(); |
| |
| return NodeOrigin(semantic, forExit, m_exitOK); |
| } |
| |
| BranchData* branchData(unsigned taken, unsigned notTaken) |
| { |
| // We assume that branches originating from bytecode always have a fall-through. We |
| // use this assumption to avoid checking for the creation of terminal blocks. |
| ASSERT((taken > m_currentIndex.offset()) || (notTaken > m_currentIndex.offset())); |
| BranchData* data = m_graph.m_branchData.add(); |
| *data = BranchData::withBytecodeIndices(taken, notTaken); |
| return data; |
| } |
| |
| Node* addToGraph(Node* node) |
| { |
| VERBOSE_LOG(" appended ", node, " ", Graph::opName(node->op()), "\n"); |
| |
| m_hasAnyForceOSRExits |= (node->op() == ForceOSRExit); |
| |
| m_currentBlock->append(node); |
| if (clobbersExitState(m_graph, node)) |
| m_exitOK = false; |
| return node; |
| } |
| |
| Node* addToGraph(NodeType op, Node* child1 = 0, Node* child2 = 0, Node* child3 = 0) |
| { |
| Node* result = m_graph.addNode( |
| op, currentNodeOrigin(), Edge(child1), Edge(child2), |
| Edge(child3)); |
| return addToGraph(result); |
| } |
| Node* addToGraph(NodeType op, Edge child1, Edge child2 = Edge(), Edge child3 = Edge()) |
| { |
| Node* result = m_graph.addNode( |
| op, currentNodeOrigin(), child1, child2, child3); |
| return addToGraph(result); |
| } |
| Node* addToGraph(NodeType op, OpInfo info, Node* child1 = 0, Node* child2 = 0, Node* child3 = 0) |
| { |
| Node* result = m_graph.addNode( |
| op, currentNodeOrigin(), info, Edge(child1), Edge(child2), |
| Edge(child3)); |
| return addToGraph(result); |
| } |
| Node* addToGraph(NodeType op, OpInfo info, Edge child1, Edge child2 = Edge(), Edge child3 = Edge()) |
| { |
| Node* result = m_graph.addNode(op, currentNodeOrigin(), info, child1, child2, child3); |
| return addToGraph(result); |
| } |
| Node* addToGraph(NodeType op, OpInfo info1, OpInfo info2, Node* child1 = 0, Node* child2 = 0, Node* child3 = 0) |
| { |
| Node* result = m_graph.addNode( |
| op, currentNodeOrigin(), info1, info2, |
| Edge(child1), Edge(child2), Edge(child3)); |
| return addToGraph(result); |
| } |
| Node* addToGraph(NodeType op, Operand operand, Node* child1) |
| { |
| ASSERT(op == MovHint); |
| return addToGraph(op, OpInfo(operand.kind()), OpInfo(operand.value()), child1); |
| } |
| Node* addToGraph(NodeType op, OpInfo info1, OpInfo info2, Edge child1, Edge child2 = Edge(), Edge child3 = Edge()) |
| { |
| Node* result = m_graph.addNode( |
| op, currentNodeOrigin(), info1, info2, child1, child2, child3); |
| return addToGraph(result); |
| } |
| |
| Node* addToGraph(Node::VarArgTag, NodeType op, OpInfo info1, OpInfo info2 = OpInfo()) |
| { |
| Node* result = m_graph.addNode( |
| Node::VarArg, op, currentNodeOrigin(), info1, info2, |
| m_graph.m_varArgChildren.size() - m_numPassedVarArgs, m_numPassedVarArgs); |
| addToGraph(result); |
| |
| m_numPassedVarArgs = 0; |
| |
| return result; |
| } |
| |
| void addVarArgChild(Node* child) |
| { |
| m_graph.m_varArgChildren.append(Edge(child)); |
| m_numPassedVarArgs++; |
| } |
| |
| void addVarArgChild(Edge child) |
| { |
| m_graph.m_varArgChildren.append(child); |
| m_numPassedVarArgs++; |
| } |
| |
| Node* addCallWithoutSettingResult( |
| NodeType op, OpInfo opInfo, Node* callee, int argCount, int registerOffset, |
| OpInfo prediction) |
| { |
| addVarArgChild(callee); |
| size_t parameterSlots = Graph::parameterSlotsForArgCount(argCount); |
| |
| if (parameterSlots > m_parameterSlots) |
| m_parameterSlots = parameterSlots; |
| |
| for (int i = 0; i < argCount; ++i) |
| addVarArgChild(get(virtualRegisterForArgument(i, registerOffset))); |
| |
| return addToGraph(Node::VarArg, op, opInfo, prediction); |
| } |
| |
| Node* addCall( |
| VirtualRegister result, NodeType op, const DOMJIT::Signature* signature, Node* callee, int argCount, int registerOffset, |
| SpeculatedType prediction) |
| { |
| if (op == TailCall) { |
| if (allInlineFramesAreTailCalls()) |
| return addCallWithoutSettingResult(op, OpInfo(signature), callee, argCount, registerOffset, OpInfo()); |
| op = TailCallInlinedCaller; |
| } |
| |
| |
| Node* call = addCallWithoutSettingResult( |
| op, OpInfo(signature), callee, argCount, registerOffset, OpInfo(prediction)); |
| if (result.isValid()) |
| set(result, call); |
| return call; |
| } |
| |
| Node* cellConstantWithStructureCheck(JSCell* object, Structure* structure) |
| { |
| // FIXME: This should route to emitPropertyCheck, not the other way around. But currently, |
| // this gets no profit from using emitPropertyCheck() since we'll non-adaptively watch the |
| // object's structure as soon as we make it a weakJSCosntant. |
| Node* objectNode = weakJSConstant(object); |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(structure)), objectNode); |
| return objectNode; |
| } |
| |
| SpeculatedType getPredictionWithoutOSRExit(BytecodeIndex bytecodeIndex) |
| { |
| auto getValueProfilePredictionFromForCodeBlockAndBytecodeOffset = [&] (CodeBlock* codeBlock, const CodeOrigin& codeOrigin) |
| { |
| SpeculatedType prediction; |
| { |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| prediction = codeBlock->valueProfilePredictionForBytecodeIndex(locker, codeOrigin.bytecodeIndex()); |
| } |
| auto* fuzzerAgent = m_vm->fuzzerAgent(); |
| if (UNLIKELY(fuzzerAgent)) |
| return fuzzerAgent->getPrediction(codeBlock, codeOrigin, prediction) & SpecBytecodeTop; |
| return prediction; |
| }; |
| |
| SpeculatedType prediction = getValueProfilePredictionFromForCodeBlockAndBytecodeOffset(m_inlineStackTop->m_profiledBlock, CodeOrigin(bytecodeIndex, inlineCallFrame())); |
| if (prediction != SpecNone) |
| return prediction; |
| |
| // If we have no information about the values this |
| // node generates, we check if by any chance it is |
| // a tail call opcode. In that case, we walk up the |
| // inline frames to find a call higher in the call |
| // chain and use its prediction. If we only have |
| // inlined tail call frames, we use SpecFullTop |
| // to avoid a spurious OSR exit. |
| auto instruction = m_inlineStackTop->m_profiledBlock->instructions().at(bytecodeIndex.offset()); |
| OpcodeID opcodeID = instruction->opcodeID(); |
| |
| switch (opcodeID) { |
| case op_tail_call: |
| case op_tail_call_varargs: |
| case op_tail_call_forward_arguments: { |
| // Things should be more permissive to us returning BOTTOM instead of TOP here. |
| // Currently, this will cause us to Force OSR exit. This is bad because returning |
| // TOP will cause anything that transitively touches this speculated type to |
| // also become TOP during prediction propagation. |
| // https://bugs.webkit.org/show_bug.cgi?id=164337 |
| if (!inlineCallFrame()) |
| return SpecFullTop; |
| |
| CodeOrigin* codeOrigin = inlineCallFrame()->getCallerSkippingTailCalls(); |
| if (!codeOrigin) |
| return SpecFullTop; |
| |
| InlineStackEntry* stack = m_inlineStackTop; |
| while (stack->m_inlineCallFrame != codeOrigin->inlineCallFrame()) |
| stack = stack->m_caller; |
| |
| return getValueProfilePredictionFromForCodeBlockAndBytecodeOffset(stack->m_profiledBlock, *codeOrigin); |
| } |
| |
| default: |
| return SpecNone; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return SpecNone; |
| } |
| |
| SpeculatedType getPrediction(BytecodeIndex bytecodeIndex) |
| { |
| SpeculatedType prediction = getPredictionWithoutOSRExit(bytecodeIndex); |
| |
| if (prediction == SpecNone) { |
| // We have no information about what values this node generates. Give up |
| // on executing this code, since we're likely to do more damage than good. |
| addToGraph(ForceOSRExit); |
| } |
| |
| return prediction; |
| } |
| |
| SpeculatedType getPredictionWithoutOSRExit() |
| { |
| return getPredictionWithoutOSRExit(m_currentIndex); |
| } |
| |
| SpeculatedType getPrediction() |
| { |
| return getPrediction(m_currentIndex); |
| } |
| |
| ArrayMode getArrayMode(Array::Action action) |
| { |
| CodeBlock* codeBlock = m_inlineStackTop->m_profiledBlock; |
| ArrayProfile* profile = codeBlock->getArrayProfile(codeBlock->bytecodeIndex(m_currentInstruction)); |
| return getArrayMode(*profile, action); |
| } |
| |
| ArrayMode getArrayMode(ArrayProfile& profile, Array::Action action) |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| profile.computeUpdatedPrediction(locker, m_inlineStackTop->m_profiledBlock); |
| bool makeSafe = profile.outOfBounds(locker); |
| return ArrayMode::fromObserved(locker, &profile, action, makeSafe); |
| } |
| |
| Node* makeSafe(Node* node) |
| { |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Overflow)) |
| node->mergeFlags(NodeMayOverflowInt32InDFG); |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, NegativeZero)) |
| node->mergeFlags(NodeMayNegZeroInDFG); |
| |
| if (!isX86() && (node->op() == ArithMod || node->op() == ValueMod)) |
| return node; |
| |
| switch (node->op()) { |
| case ArithAdd: |
| case ArithSub: |
| case ValueAdd: { |
| ObservedResults observed; |
| if (BinaryArithProfile* arithProfile = m_inlineStackTop->m_profiledBlock->binaryArithProfileForBytecodeIndex(m_currentIndex)) |
| observed = arithProfile->observedResults(); |
| else if (UnaryArithProfile* arithProfile = m_inlineStackTop->m_profiledBlock->unaryArithProfileForBytecodeIndex(m_currentIndex)) { |
| // Happens for OpInc/OpDec |
| observed = arithProfile->observedResults(); |
| } else |
| break; |
| |
| if (observed.didObserveDouble()) |
| node->mergeFlags(NodeMayHaveDoubleResult); |
| if (observed.didObserveNonNumeric()) |
| node->mergeFlags(NodeMayHaveNonNumericResult); |
| if (observed.didObserveBigInt()) |
| node->mergeFlags(NodeMayHaveBigIntResult); |
| break; |
| } |
| case ValueMul: |
| case ArithMul: { |
| BinaryArithProfile* arithProfile = m_inlineStackTop->m_profiledBlock->binaryArithProfileForBytecodeIndex(m_currentIndex); |
| if (!arithProfile) |
| break; |
| if (arithProfile->didObserveInt52Overflow()) |
| node->mergeFlags(NodeMayOverflowInt52); |
| if (arithProfile->didObserveInt32Overflow() || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Overflow)) |
| node->mergeFlags(NodeMayOverflowInt32InBaseline); |
| if (arithProfile->didObserveNegZeroDouble() || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, NegativeZero)) |
| node->mergeFlags(NodeMayNegZeroInBaseline); |
| if (arithProfile->didObserveDouble()) |
| node->mergeFlags(NodeMayHaveDoubleResult); |
| if (arithProfile->didObserveNonNumeric()) |
| node->mergeFlags(NodeMayHaveNonNumericResult); |
| if (arithProfile->didObserveBigInt()) |
| node->mergeFlags(NodeMayHaveBigIntResult); |
| break; |
| } |
| case ValueNegate: |
| case ArithNegate: |
| case Inc: |
| case Dec: { |
| UnaryArithProfile* arithProfile = m_inlineStackTop->m_profiledBlock->unaryArithProfileForBytecodeIndex(m_currentIndex); |
| if (!arithProfile) |
| break; |
| if (arithProfile->argObservedType().sawNumber() || arithProfile->didObserveDouble()) |
| node->mergeFlags(NodeMayHaveDoubleResult); |
| if (arithProfile->didObserveNegZeroDouble() || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, NegativeZero)) |
| node->mergeFlags(NodeMayNegZeroInBaseline); |
| if (arithProfile->didObserveInt32Overflow() || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Overflow)) |
| node->mergeFlags(NodeMayOverflowInt32InBaseline); |
| if (arithProfile->didObserveNonNumeric()) |
| node->mergeFlags(NodeMayHaveNonNumericResult); |
| if (arithProfile->didObserveBigInt()) |
| node->mergeFlags(NodeMayHaveBigIntResult); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| if (m_inlineStackTop->m_profiledBlock->likelyToTakeSlowCase(m_currentIndex)) { |
| switch (node->op()) { |
| case UInt32ToNumber: |
| case ArithAdd: |
| case ArithSub: |
| case ValueAdd: |
| case ValueMod: |
| case ArithMod: // for ArithMod "MayOverflow" means we tried to divide by zero, or we saw double. |
| node->mergeFlags(NodeMayOverflowInt32InBaseline); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return node; |
| } |
| |
| Node* makeDivSafe(Node* node) |
| { |
| ASSERT(node->op() == ArithDiv || node->op() == ValueDiv); |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Overflow)) |
| node->mergeFlags(NodeMayOverflowInt32InDFG); |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, NegativeZero)) |
| node->mergeFlags(NodeMayNegZeroInDFG); |
| |
| // The main slow case counter for op_div in the old JIT counts only when |
| // the operands are not numbers. We don't care about that since we already |
| // have speculations in place that take care of that separately. We only |
| // care about when the outcome of the division is not an integer, which |
| // is what the special fast case counter tells us. |
| |
| if (!m_inlineStackTop->m_profiledBlock->couldTakeSpecialArithFastCase(m_currentIndex)) |
| return node; |
| |
| // FIXME: It might be possible to make this more granular. |
| node->mergeFlags(NodeMayOverflowInt32InBaseline | NodeMayNegZeroInBaseline); |
| |
| BinaryArithProfile* arithProfile = m_inlineStackTop->m_profiledBlock->binaryArithProfileForBytecodeIndex(m_currentIndex); |
| if (arithProfile->didObserveBigInt()) |
| node->mergeFlags(NodeMayHaveBigIntResult); |
| |
| return node; |
| } |
| |
| void noticeArgumentsUse() |
| { |
| // All of the arguments in this function need to be formatted as JSValues because we will |
| // load from them in a random-access fashion and we don't want to have to switch on |
| // format. |
| |
| for (ArgumentPosition* argument : m_inlineStackTop->m_argumentPositions) |
| argument->mergeShouldNeverUnbox(true); |
| } |
| |
| bool needsDynamicLookup(ResolveType, OpcodeID); |
| |
| VM* m_vm; |
| CodeBlock* m_codeBlock; |
| CodeBlock* m_profiledBlock; |
| Graph& m_graph; |
| |
| // The current block being generated. |
| BasicBlock* m_currentBlock; |
| // The bytecode index of the current instruction being generated. |
| BytecodeIndex m_currentIndex; |
| // The semantic origin of the current node if different from the current Index. |
| CodeOrigin m_currentSemanticOrigin; |
| // True if it's OK to OSR exit right now. |
| bool m_exitOK { false }; |
| |
| FrozenValue* m_constantUndefined; |
| FrozenValue* m_constantNull; |
| FrozenValue* m_constantNaN; |
| FrozenValue* m_constantOne; |
| Vector<Node*, 16> m_constants; |
| |
| HashMap<InlineCallFrame*, Vector<ArgumentPosition*>, WTF::DefaultHash<InlineCallFrame*>::Hash, WTF::NullableHashTraits<InlineCallFrame*>> m_inlineCallFrameToArgumentPositions; |
| |
| // The number of arguments passed to the function. |
| unsigned m_numArguments; |
| // The number of locals (vars + temporaries) used by the bytecode for the function. |
| unsigned m_numLocals; |
| // The max number of temps used for forwarding data to an OSR exit checkpoint. |
| unsigned m_numTmps; |
| // The number of slots (in units of sizeof(Register)) that we need to |
| // preallocate for arguments to outgoing calls from this frame. This |
| // number includes the CallFrame slots that we initialize for the callee |
| // (but not the callee-initialized CallerFrame and ReturnPC slots). |
| // This number is 0 if and only if this function is a leaf. |
| unsigned m_parameterSlots; |
| // The number of var args passed to the next var arg node. |
| unsigned m_numPassedVarArgs; |
| |
| struct InlineStackEntry { |
| ByteCodeParser* m_byteCodeParser; |
| |
| CodeBlock* m_codeBlock; |
| CodeBlock* m_profiledBlock; |
| InlineCallFrame* m_inlineCallFrame; |
| |
| ScriptExecutable* executable() { return m_codeBlock->ownerExecutable(); } |
| |
| QueryableExitProfile m_exitProfile; |
| |
| // Remapping of identifier and constant numbers from the code block being |
| // inlined (inline callee) to the code block that we're inlining into |
| // (the machine code block, which is the transitive, though not necessarily |
| // direct, caller). |
| Vector<unsigned> m_identifierRemap; |
| Vector<unsigned> m_switchRemap; |
| |
| // These are blocks whose terminal is a Jump, Branch or Switch, and whose target has not yet been linked. |
| // Their terminal instead refers to a bytecode index, and the right BB can be found in m_blockLinkingTargets. |
| Vector<BasicBlock*> m_unlinkedBlocks; |
| |
| // Potential block linking targets. Must be sorted by bytecodeBegin, and |
| // cannot have two blocks that have the same bytecodeBegin. |
| Vector<BasicBlock*> m_blockLinkingTargets; |
| |
| // Optional: a continuation block for returns to jump to. It is set by early returns if it does not exist. |
| BasicBlock* m_continuationBlock; |
| |
| VirtualRegister m_returnValue; |
| |
| // Speculations about variable types collected from the profiled code block, |
| // which are based on OSR exit profiles that past DFG compilations of this |
| // code block had gathered. |
| LazyOperandValueProfileParser m_lazyOperands; |
| |
| ICStatusMap m_baselineMap; |
| ICStatusContext m_optimizedContext; |
| |
| // Pointers to the argument position trackers for this slice of code. |
| Vector<ArgumentPosition*> m_argumentPositions; |
| |
| InlineStackEntry* m_caller; |
| |
| InlineStackEntry( |
| ByteCodeParser*, |
| CodeBlock*, |
| CodeBlock* profiledBlock, |
| JSFunction* callee, // Null if this is a closure call. |
| VirtualRegister returnValueVR, |
| VirtualRegister inlineCallFrameStart, |
| int argumentCountIncludingThis, |
| InlineCallFrame::Kind, |
| BasicBlock* continuationBlock); |
| |
| ~InlineStackEntry(); |
| |
| Operand remapOperand(Operand operand) const |
| { |
| if (!m_inlineCallFrame) |
| return operand; |
| |
| if (operand.isTmp()) |
| return Operand::tmp(operand.value() + m_inlineCallFrame->tmpOffset); |
| |
| ASSERT(!operand.virtualRegister().isConstant()); |
| |
| return operand.virtualRegister() + m_inlineCallFrame->stackOffset; |
| } |
| }; |
| |
| InlineStackEntry* m_inlineStackTop; |
| |
| ICStatusContextStack m_icContextStack; |
| |
| struct DelayedSetLocal { |
| DelayedSetLocal() { } |
| DelayedSetLocal(const CodeOrigin& origin, Operand operand, Node* value, SetMode setMode) |
| : m_origin(origin) |
| , m_operand(operand) |
| , m_value(value) |
| , m_setMode(setMode) |
| { |
| RELEASE_ASSERT(operand.isValid()); |
| } |
| |
| Node* execute(ByteCodeParser* parser) |
| { |
| if (m_operand.isArgument()) |
| return parser->setArgument(m_origin, m_operand, m_value, m_setMode); |
| return parser->setLocalOrTmp(m_origin, m_operand, m_value, m_setMode); |
| } |
| |
| CodeOrigin m_origin; |
| Operand m_operand; |
| Node* m_value { nullptr }; |
| SetMode m_setMode; |
| }; |
| |
| Vector<DelayedSetLocal, 2> m_setLocalQueue; |
| |
| const Instruction* m_currentInstruction; |
| bool m_hasDebuggerEnabled; |
| bool m_hasAnyForceOSRExits { false }; |
| }; |
| |
| BasicBlock* ByteCodeParser::allocateTargetableBlock(BytecodeIndex bytecodeIndex) |
| { |
| ASSERT(bytecodeIndex); |
| Ref<BasicBlock> block = adoptRef(*new BasicBlock(bytecodeIndex, m_numArguments, m_numLocals, m_numTmps, 1)); |
| BasicBlock* blockPtr = block.ptr(); |
| // m_blockLinkingTargets must always be sorted in increasing order of bytecodeBegin |
| if (m_inlineStackTop->m_blockLinkingTargets.size()) |
| ASSERT(m_inlineStackTop->m_blockLinkingTargets.last()->bytecodeBegin.offset() < bytecodeIndex.offset()); |
| m_inlineStackTop->m_blockLinkingTargets.append(blockPtr); |
| m_graph.appendBlock(WTFMove(block)); |
| return blockPtr; |
| } |
| |
| BasicBlock* ByteCodeParser::allocateUntargetableBlock() |
| { |
| Ref<BasicBlock> block = adoptRef(*new BasicBlock(BytecodeIndex(), m_numArguments, m_numLocals, m_numTmps, 1)); |
| BasicBlock* blockPtr = block.ptr(); |
| m_graph.appendBlock(WTFMove(block)); |
| return blockPtr; |
| } |
| |
| void ByteCodeParser::makeBlockTargetable(BasicBlock* block, BytecodeIndex bytecodeIndex) |
| { |
| RELEASE_ASSERT(!block->bytecodeBegin); |
| block->bytecodeBegin = bytecodeIndex; |
| // m_blockLinkingTargets must always be sorted in increasing order of bytecodeBegin |
| if (m_inlineStackTop->m_blockLinkingTargets.size()) |
| ASSERT(m_inlineStackTop->m_blockLinkingTargets.last()->bytecodeBegin.offset() < bytecodeIndex.offset()); |
| m_inlineStackTop->m_blockLinkingTargets.append(block); |
| } |
| |
| void ByteCodeParser::addJumpTo(BasicBlock* block) |
| { |
| ASSERT(!m_currentBlock->terminal()); |
| Node* jumpNode = addToGraph(Jump); |
| jumpNode->targetBlock() = block; |
| m_currentBlock->didLink(); |
| } |
| |
| void ByteCodeParser::addJumpTo(unsigned bytecodeIndex) |
| { |
| ASSERT(!m_currentBlock->terminal()); |
| addToGraph(Jump, OpInfo(bytecodeIndex)); |
| m_inlineStackTop->m_unlinkedBlocks.append(m_currentBlock); |
| } |
| |
| template<typename CallOp> |
| ByteCodeParser::Terminality ByteCodeParser::handleCall(const Instruction* pc, NodeType op, CallMode callMode) |
| { |
| auto bytecode = pc->as<CallOp>(); |
| Node* callTarget = get(bytecode.m_callee); |
| int registerOffset = -static_cast<int>(bytecode.m_argv); |
| |
| CallLinkStatus callLinkStatus = CallLinkStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, currentCodeOrigin(), |
| m_inlineStackTop->m_baselineMap, m_icContextStack); |
| |
| InlineCallFrame::Kind kind = InlineCallFrame::kindFor(callMode); |
| |
| return handleCall(bytecode.m_dst, op, kind, pc->size(), callTarget, |
| bytecode.m_argc, registerOffset, callLinkStatus, getPrediction()); |
| } |
| |
| void ByteCodeParser::refineStatically(CallLinkStatus& callLinkStatus, Node* callTarget) |
| { |
| if (callTarget->isCellConstant()) |
| callLinkStatus.setProvenConstantCallee(CallVariant(callTarget->asCell())); |
| } |
| |
| ByteCodeParser::Terminality ByteCodeParser::handleCall( |
| VirtualRegister result, NodeType op, InlineCallFrame::Kind kind, unsigned instructionSize, |
| Node* callTarget, int argumentCountIncludingThis, int registerOffset, |
| CallLinkStatus callLinkStatus, SpeculatedType prediction) |
| { |
| ASSERT(registerOffset <= 0); |
| |
| refineStatically(callLinkStatus, callTarget); |
| |
| VERBOSE_LOG(" Handling call at ", currentCodeOrigin(), ": ", callLinkStatus, "\n"); |
| |
| // If we have profiling information about this call, and it did not behave too polymorphically, |
| // we may be able to inline it, or in the case of recursive tail calls turn it into a jump. |
| if (callLinkStatus.canOptimize()) { |
| addToGraph(FilterCallLinkStatus, OpInfo(m_graph.m_plan.recordedStatuses().addCallLinkStatus(currentCodeOrigin(), callLinkStatus)), callTarget); |
| |
| VirtualRegister thisArgument = virtualRegisterForArgument(0, registerOffset); |
| auto optimizationResult = handleInlining(callTarget, result, callLinkStatus, registerOffset, thisArgument, |
| argumentCountIncludingThis, BytecodeIndex(m_currentIndex.offset() + instructionSize), op, kind, prediction); |
| if (optimizationResult == CallOptimizationResult::OptimizedToJump) |
| return Terminal; |
| if (optimizationResult == CallOptimizationResult::Inlined) { |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedCall(); |
| return NonTerminal; |
| } |
| } |
| |
| Node* callNode = addCall(result, op, nullptr, callTarget, argumentCountIncludingThis, registerOffset, prediction); |
| ASSERT(callNode->op() != TailCallVarargs && callNode->op() != TailCallForwardVarargs); |
| return callNode->op() == TailCall ? Terminal : NonTerminal; |
| } |
| |
| template<typename CallOp> |
| ByteCodeParser::Terminality ByteCodeParser::handleVarargsCall(const Instruction* pc, NodeType op, CallMode callMode) |
| { |
| auto bytecode = pc->as<CallOp>(); |
| int firstFreeReg = bytecode.m_firstFree.offset(); |
| int firstVarArgOffset = bytecode.m_firstVarArg; |
| |
| SpeculatedType prediction = getPrediction(); |
| |
| Node* callTarget = get(bytecode.m_callee); |
| |
| CallLinkStatus callLinkStatus = CallLinkStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, currentCodeOrigin(), |
| m_inlineStackTop->m_baselineMap, m_icContextStack); |
| refineStatically(callLinkStatus, callTarget); |
| |
| VERBOSE_LOG(" Varargs call link status at ", currentCodeOrigin(), ": ", callLinkStatus, "\n"); |
| |
| if (callLinkStatus.canOptimize()) { |
| addToGraph(FilterCallLinkStatus, OpInfo(m_graph.m_plan.recordedStatuses().addCallLinkStatus(currentCodeOrigin(), callLinkStatus)), callTarget); |
| |
| if (handleVarargsInlining(callTarget, bytecode.m_dst, |
| callLinkStatus, firstFreeReg, bytecode.m_thisValue, bytecode.m_arguments, |
| firstVarArgOffset, op, |
| InlineCallFrame::varargsKindFor(callMode))) { |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedCall(); |
| return NonTerminal; |
| } |
| } |
| |
| CallVarargsData* data = m_graph.m_callVarargsData.add(); |
| data->firstVarArgOffset = firstVarArgOffset; |
| |
| Node* thisChild = get(bytecode.m_thisValue); |
| Node* argumentsChild = nullptr; |
| if (op != TailCallForwardVarargs) |
| argumentsChild = get(bytecode.m_arguments); |
| |
| if (op == TailCallVarargs || op == TailCallForwardVarargs) { |
| if (allInlineFramesAreTailCalls()) { |
| addToGraph(op, OpInfo(data), OpInfo(), callTarget, thisChild, argumentsChild); |
| return Terminal; |
| } |
| op = op == TailCallVarargs ? TailCallVarargsInlinedCaller : TailCallForwardVarargsInlinedCaller; |
| } |
| |
| Node* call = addToGraph(op, OpInfo(data), OpInfo(prediction), callTarget, thisChild, argumentsChild); |
| if (bytecode.m_dst.isValid()) |
| set(bytecode.m_dst, call); |
| return NonTerminal; |
| } |
| |
| void ByteCodeParser::emitFunctionChecks(CallVariant callee, Node* callTarget, VirtualRegister thisArgumentReg) |
| { |
| Node* thisArgument; |
| if (thisArgumentReg.isValid()) |
| thisArgument = get(thisArgumentReg); |
| else |
| thisArgument = nullptr; |
| |
| JSCell* calleeCell; |
| Node* callTargetForCheck; |
| if (callee.isClosureCall()) { |
| calleeCell = callee.executable(); |
| callTargetForCheck = addToGraph(GetExecutable, callTarget); |
| } else { |
| calleeCell = callee.nonExecutableCallee(); |
| callTargetForCheck = callTarget; |
| } |
| |
| ASSERT(calleeCell); |
| addToGraph(CheckCell, OpInfo(m_graph.freeze(calleeCell)), callTargetForCheck); |
| if (thisArgument) |
| addToGraph(Phantom, thisArgument); |
| } |
| |
| Node* ByteCodeParser::getArgumentCount() |
| { |
| Node* argumentCount; |
| if (m_inlineStackTop->m_inlineCallFrame && !m_inlineStackTop->m_inlineCallFrame->isVarargs()) |
| argumentCount = jsConstant(m_graph.freeze(jsNumber(m_inlineStackTop->m_inlineCallFrame->argumentCountIncludingThis))->value()); |
| else |
| argumentCount = addToGraph(GetArgumentCountIncludingThis, OpInfo(m_inlineStackTop->m_inlineCallFrame), OpInfo(SpecInt32Only)); |
| return argumentCount; |
| } |
| |
| void ByteCodeParser::emitArgumentPhantoms(int registerOffset, int argumentCountIncludingThis) |
| { |
| for (int i = 0; i < argumentCountIncludingThis; ++i) |
| addToGraph(Phantom, get(virtualRegisterForArgument(i, registerOffset))); |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleRecursiveTailCall(Node* callTargetNode, CallVariant callVariant, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& emitFunctionCheckIfNeeded) |
| { |
| if (UNLIKELY(!Options::optimizeRecursiveTailCalls())) |
| return false; |
| |
| auto targetExecutable = callVariant.executable(); |
| InlineStackEntry* stackEntry = m_inlineStackTop; |
| do { |
| if (targetExecutable != stackEntry->executable()) |
| continue; |
| VERBOSE_LOG(" We found a recursive tail call, trying to optimize it into a jump.\n"); |
| |
| if (auto* callFrame = stackEntry->m_inlineCallFrame) { |
| // FIXME: We only accept jump to CallFrame which has exact same argumentCountIncludingThis. But we can remove this by fixing up arguments. |
| // And we can also allow jumping into CallFrame with Varargs if the passing number of arguments is greater than or equal to mandatoryMinimum of CallFrame. |
| // https://bugs.webkit.org/show_bug.cgi?id=202317 |
| |
| // Some code may statically use the argument count from the InlineCallFrame, so it would be invalid to loop back if it does not match. |
| // We "continue" instead of returning false in case another stack entry further on the stack has the right number of arguments. |
| if (argumentCountIncludingThis != static_cast<int>(callFrame->argumentCountIncludingThis)) |
| continue; |
| // If the target InlineCallFrame is Varargs, we do not know how many arguments are actually filled by LoadVarargs. Varargs InlineCallFrame's |
| // argumentCountIncludingThis is maximum number of potentially filled arguments by xkLoadVarargs. We "continue" to the upper frame which may be |
| // a good target to jump into. |
| if (callFrame->isVarargs()) |
| continue; |
| } else { |
| // We are in the machine code entry (i.e. the original caller). |
| // If we have more arguments than the number of parameters to the function, it is not clear where we could put them on the stack. |
| if (argumentCountIncludingThis > m_codeBlock->numParameters()) |
| return false; |
| } |
| |
| // If an InlineCallFrame is not a closure, it was optimized using a constant callee. |
| // Check if this is the same callee that we try to inline here. |
| if (stackEntry->m_inlineCallFrame && !stackEntry->m_inlineCallFrame->isClosureCall) { |
| if (stackEntry->m_inlineCallFrame->calleeConstant() != callVariant.function()) |
| continue; |
| } |
| |
| // We must add some check that the profiling information was correct and the target of this call is what we thought. |
| emitFunctionCheckIfNeeded(); |
| // We flush everything, as if we were in the backedge of a loop (see treatment of op_jmp in parseBlock). |
| flushForTerminal(); |
| |
| // We must set the callee to the right value |
| if (stackEntry->m_inlineCallFrame) { |
| if (stackEntry->m_inlineCallFrame->isClosureCall) |
| setDirect(remapOperand(stackEntry->m_inlineCallFrame, CallFrameSlot::callee), callTargetNode, NormalSet); |
| } else |
| addToGraph(SetCallee, callTargetNode); |
| |
| // We must set the arguments to the right values |
| if (!stackEntry->m_inlineCallFrame) |
| addToGraph(SetArgumentCountIncludingThis, OpInfo(argumentCountIncludingThis)); |
| int argIndex = 0; |
| for (; argIndex < argumentCountIncludingThis; ++argIndex) { |
| Node* value = get(virtualRegisterForArgument(argIndex, registerOffset)); |
| setDirect(stackEntry->remapOperand(virtualRegisterForArgument(argIndex)), value, NormalSet); |
| } |
| Node* undefined = addToGraph(JSConstant, OpInfo(m_constantUndefined)); |
| for (; argIndex < stackEntry->m_codeBlock->numParameters(); ++argIndex) |
| setDirect(stackEntry->remapOperand(virtualRegisterForArgument(argIndex)), undefined, NormalSet); |
| |
| // We must repeat the work of op_enter here as we will jump right after it. |
| // We jump right after it and not before it, because of some invariant saying that a CFG root cannot have predecessors in the IR. |
| for (int i = 0; i < stackEntry->m_codeBlock->numVars(); ++i) |
| setDirect(stackEntry->remapOperand(virtualRegisterForLocal(i)), undefined, NormalSet); |
| |
| // We want to emit the SetLocals with an exit origin that points to the place we are jumping to. |
| BytecodeIndex oldIndex = m_currentIndex; |
| auto oldStackTop = m_inlineStackTop; |
| m_inlineStackTop = stackEntry; |
| m_currentIndex = BytecodeIndex(opcodeLengths[op_enter]); |
| m_exitOK = true; |
| processSetLocalQueue(); |
| m_currentIndex = oldIndex; |
| m_inlineStackTop = oldStackTop; |
| m_exitOK = false; |
| |
| BasicBlock** entryBlockPtr = tryBinarySearch<BasicBlock*, BytecodeIndex>(stackEntry->m_blockLinkingTargets, stackEntry->m_blockLinkingTargets.size(), BytecodeIndex(opcodeLengths[op_enter]), getBytecodeBeginForBlock); |
| RELEASE_ASSERT(entryBlockPtr); |
| addJumpTo(*entryBlockPtr); |
| return true; |
| // It would be unsound to jump over a non-tail call: the "tail" call is not really a tail call in that case. |
| } while (stackEntry->m_inlineCallFrame && stackEntry->m_inlineCallFrame->kind == InlineCallFrame::TailCall && (stackEntry = stackEntry->m_caller)); |
| |
| // The tail call was not recursive |
| return false; |
| } |
| |
| unsigned ByteCodeParser::inliningCost(CallVariant callee, int argumentCountIncludingThis, InlineCallFrame::Kind kind) |
| { |
| CallMode callMode = InlineCallFrame::callModeFor(kind); |
| CodeSpecializationKind specializationKind = specializationKindFor(callMode); |
| VERBOSE_LOG("Considering inlining ", callee, " into ", currentCodeOrigin(), "\n"); |
| |
| if (m_hasDebuggerEnabled) { |
| VERBOSE_LOG(" Failing because the debugger is in use.\n"); |
| return UINT_MAX; |
| } |
| |
| FunctionExecutable* executable = callee.functionExecutable(); |
| if (!executable) { |
| VERBOSE_LOG(" Failing because there is no function executable.\n"); |
| return UINT_MAX; |
| } |
| |
| // Do we have a code block, and does the code block's size match the heuristics/requirements for |
| // being an inline candidate? We might not have a code block (1) if code was thrown away, |
| // (2) if we simply hadn't actually made this call yet or (3) code is a builtin function and |
| // specialization kind is construct. In the former 2 cases, we could still theoretically attempt |
| // to inline it if we had a static proof of what was being called; this might happen for example |
| // if you call a global function, where watchpointing gives us static information. Overall, |
| // it's a rare case because we expect that any hot callees would have already been compiled. |
| CodeBlock* codeBlock = executable->baselineCodeBlockFor(specializationKind); |
| if (!codeBlock) { |
| VERBOSE_LOG(" Failing because no code block available.\n"); |
| return UINT_MAX; |
| } |
| |
| if (!Options::useArityFixupInlining()) { |
| if (codeBlock->numParameters() > argumentCountIncludingThis) { |
| VERBOSE_LOG(" Failing because of arity mismatch.\n"); |
| return UINT_MAX; |
| } |
| } |
| |
| CapabilityLevel capabilityLevel = inlineFunctionForCapabilityLevel( |
| codeBlock, specializationKind, callee.isClosureCall()); |
| VERBOSE_LOG(" Call mode: ", callMode, "\n"); |
| VERBOSE_LOG(" Is closure call: ", callee.isClosureCall(), "\n"); |
| VERBOSE_LOG(" Capability level: ", capabilityLevel, "\n"); |
| VERBOSE_LOG(" Might inline function: ", mightInlineFunctionFor(codeBlock, specializationKind), "\n"); |
| VERBOSE_LOG(" Might compile function: ", mightCompileFunctionFor(codeBlock, specializationKind), "\n"); |
| VERBOSE_LOG(" Is supported for inlining: ", isSupportedForInlining(codeBlock), "\n"); |
| VERBOSE_LOG(" Is inlining candidate: ", codeBlock->ownerExecutable()->isInliningCandidate(), "\n"); |
| if (!canInline(capabilityLevel)) { |
| VERBOSE_LOG(" Failing because the function is not inlineable.\n"); |
| return UINT_MAX; |
| } |
| |
| // Check if the caller is already too large. We do this check here because that's just |
| // where we happen to also have the callee's code block, and we want that for the |
| // purpose of unsetting SABI. |
| if (!isSmallEnoughToInlineCodeInto(m_codeBlock)) { |
| codeBlock->m_shouldAlwaysBeInlined = false; |
| VERBOSE_LOG(" Failing because the caller is too large.\n"); |
| return UINT_MAX; |
| } |
| |
| // FIXME: this should be better at predicting how much bloat we will introduce by inlining |
| // this function. |
| // https://bugs.webkit.org/show_bug.cgi?id=127627 |
| |
| // FIXME: We currently inline functions that have run in LLInt but not in Baseline. These |
| // functions have very low fidelity profiling, and presumably they weren't very hot if they |
| // haven't gotten to Baseline yet. Consider not inlining these functions. |
| // https://bugs.webkit.org/show_bug.cgi?id=145503 |
| |
| // Have we exceeded inline stack depth, or are we trying to inline a recursive call to |
| // too many levels? If either of these are detected, then don't inline. We adjust our |
| // heuristics if we are dealing with a function that cannot otherwise be compiled. |
| |
| unsigned depth = 0; |
| unsigned recursion = 0; |
| |
| for (InlineStackEntry* entry = m_inlineStackTop; entry; entry = entry->m_caller) { |
| ++depth; |
| if (depth >= Options::maximumInliningDepth()) { |
| VERBOSE_LOG(" Failing because depth exceeded.\n"); |
| return UINT_MAX; |
| } |
| |
| if (entry->executable() == executable) { |
| ++recursion; |
| if (recursion >= Options::maximumInliningRecursion()) { |
| VERBOSE_LOG(" Failing because recursion detected.\n"); |
| return UINT_MAX; |
| } |
| } |
| } |
| |
| VERBOSE_LOG(" Inlining should be possible.\n"); |
| |
| // It might be possible to inline. |
| return codeBlock->bytecodeCost(); |
| } |
| |
| template<typename ChecksFunctor> |
| void ByteCodeParser::inlineCall(Node* callTargetNode, VirtualRegister result, CallVariant callee, int registerOffset, int argumentCountIncludingThis, InlineCallFrame::Kind kind, BasicBlock* continuationBlock, const ChecksFunctor& insertChecks) |
| { |
| const Instruction* savedCurrentInstruction = m_currentInstruction; |
| CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind); |
| |
| CodeBlock* codeBlock = callee.functionExecutable()->baselineCodeBlockFor(specializationKind); |
| insertChecks(codeBlock); |
| |
| // FIXME: Don't flush constants! |
| |
| // arityFixupCount and numberOfStackPaddingSlots are different. While arityFixupCount does not consider about stack alignment, |
| // numberOfStackPaddingSlots consider alignment. Consider the following case, |
| // |
| // before: [ ... ][arg0][header] |
| // after: [ ... ][ext ][arg1][arg0][header] |
| // |
| // In the above case, arityFixupCount is 1. But numberOfStackPaddingSlots is 2 because the stack needs to be aligned. |
| // We insert extra slots to align stack. |
| int arityFixupCount = std::max<int>(codeBlock->numParameters() - argumentCountIncludingThis, 0); |
| int numberOfStackPaddingSlots = CommonSlowPaths::numberOfStackPaddingSlots(codeBlock, argumentCountIncludingThis); |
| ASSERT(!(numberOfStackPaddingSlots % stackAlignmentRegisters())); |
| int registerOffsetAfterFixup = registerOffset - numberOfStackPaddingSlots; |
| |
| Operand inlineCallFrameStart = VirtualRegister(m_inlineStackTop->remapOperand(VirtualRegister(registerOffsetAfterFixup)).value() + CallFrame::headerSizeInRegisters); |
| |
| ensureLocals( |
| inlineCallFrameStart.toLocal() + 1 + |
| CallFrame::headerSizeInRegisters + codeBlock->numCalleeLocals()); |
| |
| ensureTmps((m_inlineStackTop->m_inlineCallFrame ? m_inlineStackTop->m_inlineCallFrame->tmpOffset : 0) + m_inlineStackTop->m_codeBlock->numTmps() + codeBlock->numTmps()); |
| |
| size_t argumentPositionStart = m_graph.m_argumentPositions.size(); |
| |
| if (result.isValid()) |
| result = m_inlineStackTop->remapOperand(result).virtualRegister(); |
| |
| VariableAccessData* calleeVariable = nullptr; |
| if (callee.isClosureCall()) { |
| Node* calleeSet = set( |
| VirtualRegister(registerOffsetAfterFixup + CallFrameSlot::callee), callTargetNode, ImmediateNakedSet); |
| |
| calleeVariable = calleeSet->variableAccessData(); |
| calleeVariable->mergeShouldNeverUnbox(true); |
| } |
| |
| InlineStackEntry* callerStackTop = m_inlineStackTop; |
| InlineStackEntry inlineStackEntry(this, codeBlock, codeBlock, callee.function(), result, |
| inlineCallFrameStart.virtualRegister(), argumentCountIncludingThis, kind, continuationBlock); |
| |
| // This is where the actual inlining really happens. |
| BytecodeIndex oldIndex = m_currentIndex; |
| m_currentIndex = BytecodeIndex(0); |
| |
| switch (kind) { |
| case InlineCallFrame::GetterCall: |
| case InlineCallFrame::SetterCall: { |
| // When inlining getter and setter calls, we setup a stack frame which does not appear in the bytecode. |
| // Because Inlining can switch on executable, we could have a graph like this. |
| // |
| // BB#0 |
| // ... |
| // 30: GetSetter |
| // 31: MovHint(loc10) |
| // 32: SetLocal(loc10) |
| // 33: MovHint(loc9) |
| // 34: SetLocal(loc9) |
| // ... |
| // 37: GetExecutable(@30) |
| // ... |
| // 41: Switch(@37) |
| // |
| // BB#2 |
| // 42: GetLocal(loc12, bc#7 of caller) |
| // ... |
| // --> callee: loc9 and loc10 are arguments of callee. |
| // ... |
| // <HERE, exit to callee, loc9 and loc10 are required in the bytecode> |
| // |
| // When we prune OSR availability at the beginning of BB#2 (bc#7 in the caller), we prune loc9 and loc10's liveness because the caller does not actually have loc9 and loc10. |
| // However, when we begin executing the callee, we need OSR exit to be aware of where it can recover the arguments to the setter, loc9 and loc10. The MovHints in the inlined |
| // callee make it so that if we exit at <HERE>, we can recover loc9 and loc10. |
| for (int index = 0; index < argumentCountIncludingThis; ++index) { |
| Operand argumentToGet = callerStackTop->remapOperand(virtualRegisterForArgument(index, registerOffset)); |
| Node* value = getDirect(argumentToGet); |
| addToGraph(MovHint, OpInfo(argumentToGet), value); |
| m_setLocalQueue.append(DelayedSetLocal { currentCodeOrigin(), argumentToGet, value, ImmediateNakedSet }); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (arityFixupCount) { |
| // Note: we do arity fixup in two phases: |
| // 1. We get all the values we need and MovHint them to the expected locals. |
| // 2. We SetLocal them after that. This way, if we exit, the callee's |
| // frame is already set up. If any SetLocal exits, we have a valid exit state. |
| // This is required because if we didn't do this in two phases, we may exit in |
| // the middle of arity fixup from the callee's CodeOrigin. This is unsound because exited |
| // code does not have arity fixup so that remaining necessary fixups are not executed. |
| // For example, consider if we need to pad two args: |
| // [arg3][arg2][arg1][arg0] |
| // [fix ][fix ][arg3][arg2][arg1][arg0] |
| // We memcpy starting from arg0 in the direction of arg3. If we were to exit at a type check |
| // for arg3's SetLocal in the callee's CodeOrigin, we'd exit with a frame like so: |
| // [arg3][arg2][arg1][arg2][arg1][arg0] |
| // Since we do not perform arity fixup in the callee, this is the frame used by the callee. |
| // And the callee would then just end up thinking its argument are: |
| // [fix ][fix ][arg3][arg2][arg1][arg0] |
| // which is incorrect. |
| |
| Node* undefined = addToGraph(JSConstant, OpInfo(m_constantUndefined)); |
| // The stack needs to be aligned due to the JS calling convention. Thus, we have a hole if the count of arguments is not aligned. |
| // We call this hole "extra slot". Consider the following case, the number of arguments is 2. If this argument |
| // count does not fulfill the stack alignment requirement, we already inserted extra slots. |
| // |
| // before: [ ... ][ext ][arg1][arg0][header] |
| // |
| // In the above case, one extra slot is inserted. If the code's parameter count is 3, we will fixup arguments. |
| // At that time, we can simply use this extra slots. So the fixuped stack is the following. |
| // |
| // before: [ ... ][ext ][arg1][arg0][header] |
| // after: [ ... ][arg2][arg1][arg0][header] |
| // |
| // In such cases, we do not need to move frames. |
| if (registerOffsetAfterFixup != registerOffset) { |
| for (int index = 0; index < argumentCountIncludingThis; ++index) { |
| Operand argumentToGet = callerStackTop->remapOperand(virtualRegisterForArgument(index, registerOffset)); |
| Node* value = getDirect(argumentToGet); |
| Operand argumentToSet = m_inlineStackTop->remapOperand(virtualRegisterForArgument(index)); |
| addToGraph(MovHint, OpInfo(argumentToSet), value); |
| m_setLocalQueue.append(DelayedSetLocal { currentCodeOrigin(), argumentToSet, value, ImmediateNakedSet }); |
| } |
| } |
| for (int index = 0; index < arityFixupCount; ++index) { |
| Operand argumentToSet = m_inlineStackTop->remapOperand(virtualRegisterForArgument(argumentCountIncludingThis + index)); |
| addToGraph(MovHint, OpInfo(argumentToSet), undefined); |
| m_setLocalQueue.append(DelayedSetLocal { currentCodeOrigin(), argumentToSet, undefined, ImmediateNakedSet }); |
| } |
| |
| // At this point, it's OK to OSR exit because we finished setting up |
| // our callee's frame. We emit an ExitOK below. |
| } |
| |
| // At this point, it's again OK to OSR exit. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| processSetLocalQueue(); |
| |
| InlineVariableData inlineVariableData; |
| inlineVariableData.inlineCallFrame = m_inlineStackTop->m_inlineCallFrame; |
| inlineVariableData.argumentPositionStart = argumentPositionStart; |
| inlineVariableData.calleeVariable = 0; |
| |
| RELEASE_ASSERT( |
| m_inlineStackTop->m_inlineCallFrame->isClosureCall |
| == callee.isClosureCall()); |
| if (callee.isClosureCall()) { |
| RELEASE_ASSERT(calleeVariable); |
| inlineVariableData.calleeVariable = calleeVariable; |
| } |
| |
| m_graph.m_inlineVariableData.append(inlineVariableData); |
| |
| parseCodeBlock(); |
| clearCaches(); // Reset our state now that we're back to the outer code. |
| |
| m_currentIndex = oldIndex; |
| m_exitOK = false; |
| |
| linkBlocks(inlineStackEntry.m_unlinkedBlocks, inlineStackEntry.m_blockLinkingTargets); |
| |
| // Most functions have at least one op_ret and thus set up the continuation block. |
| // In some rare cases, a function ends in op_unreachable, forcing us to allocate a new continuationBlock here. |
| if (inlineStackEntry.m_continuationBlock) |
| m_currentBlock = inlineStackEntry.m_continuationBlock; |
| else |
| m_currentBlock = allocateUntargetableBlock(); |
| ASSERT(!m_currentBlock->terminal()); |
| |
| prepareToParseBlock(); |
| m_currentInstruction = savedCurrentInstruction; |
| } |
| |
| ByteCodeParser::CallOptimizationResult ByteCodeParser::handleCallVariant(Node* callTargetNode, VirtualRegister result, CallVariant callee, int registerOffset, VirtualRegister thisArgument, int argumentCountIncludingThis, BytecodeIndex nextIndex, InlineCallFrame::Kind kind, SpeculatedType prediction, unsigned& inliningBalance, BasicBlock* continuationBlock, bool needsToCheckCallee) |
| { |
| VERBOSE_LOG(" Considering callee ", callee, "\n"); |
| |
| bool didInsertChecks = false; |
| auto insertChecksWithAccounting = [&] () { |
| if (needsToCheckCallee) |
| emitFunctionChecks(callee, callTargetNode, thisArgument); |
| didInsertChecks = true; |
| }; |
| |
| if (kind == InlineCallFrame::TailCall && ByteCodeParser::handleRecursiveTailCall(callTargetNode, callee, registerOffset, argumentCountIncludingThis, insertChecksWithAccounting)) { |
| RELEASE_ASSERT(didInsertChecks); |
| return CallOptimizationResult::OptimizedToJump; |
| } |
| RELEASE_ASSERT(!didInsertChecks); |
| |
| if (!inliningBalance) |
| return CallOptimizationResult::DidNothing; |
| |
| CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind); |
| |
| auto endSpecialCase = [&] () { |
| RELEASE_ASSERT(didInsertChecks); |
| addToGraph(Phantom, callTargetNode); |
| emitArgumentPhantoms(registerOffset, argumentCountIncludingThis); |
| inliningBalance--; |
| if (continuationBlock) { |
| m_currentIndex = nextIndex; |
| m_exitOK = true; |
| processSetLocalQueue(); |
| addJumpTo(continuationBlock); |
| } |
| }; |
| |
| if (InternalFunction* function = callee.internalFunction()) { |
| if (handleConstantInternalFunction(callTargetNode, result, function, registerOffset, argumentCountIncludingThis, specializationKind, prediction, insertChecksWithAccounting)) { |
| endSpecialCase(); |
| return CallOptimizationResult::Inlined; |
| } |
| RELEASE_ASSERT(!didInsertChecks); |
| return CallOptimizationResult::DidNothing; |
| } |
| |
| Intrinsic intrinsic = callee.intrinsicFor(specializationKind); |
| if (intrinsic != NoIntrinsic) { |
| if (handleIntrinsicCall(callTargetNode, result, intrinsic, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) { |
| endSpecialCase(); |
| return CallOptimizationResult::Inlined; |
| } |
| RELEASE_ASSERT(!didInsertChecks); |
| // We might still try to inline the Intrinsic because it might be a builtin JS function. |
| } |
| |
| if (Options::useDOMJIT()) { |
| if (const DOMJIT::Signature* signature = callee.signatureFor(specializationKind)) { |
| if (handleDOMJITCall(callTargetNode, result, signature, registerOffset, argumentCountIncludingThis, prediction, insertChecksWithAccounting)) { |
| endSpecialCase(); |
| return CallOptimizationResult::Inlined; |
| } |
| RELEASE_ASSERT(!didInsertChecks); |
| } |
| } |
| |
| unsigned myInliningCost = inliningCost(callee, argumentCountIncludingThis, kind); |
| if (myInliningCost > inliningBalance) |
| return CallOptimizationResult::DidNothing; |
| |
| auto insertCheck = [&] (CodeBlock*) { |
| if (needsToCheckCallee) |
| emitFunctionChecks(callee, callTargetNode, thisArgument); |
| }; |
| inlineCall(callTargetNode, result, callee, registerOffset, argumentCountIncludingThis, kind, continuationBlock, insertCheck); |
| inliningBalance -= myInliningCost; |
| return CallOptimizationResult::Inlined; |
| } |
| |
| bool ByteCodeParser::handleVarargsInlining(Node* callTargetNode, VirtualRegister result, |
| const CallLinkStatus& callLinkStatus, int firstFreeReg, VirtualRegister thisArgument, |
| VirtualRegister argumentsArgument, unsigned argumentsOffset, |
| NodeType callOp, InlineCallFrame::Kind kind) |
| { |
| VERBOSE_LOG("Handling inlining (Varargs)...\nStack: ", currentCodeOrigin(), "\n"); |
| if (callLinkStatus.maxArgumentCountIncludingThis() > Options::maximumVarargsForInlining()) { |
| VERBOSE_LOG("Bailing inlining: too many arguments for varargs inlining.\n"); |
| return false; |
| } |
| if (callLinkStatus.couldTakeSlowPath() || callLinkStatus.size() != 1) { |
| VERBOSE_LOG("Bailing inlining: polymorphic inlining is not yet supported for varargs.\n"); |
| return false; |
| } |
| |
| CallVariant callVariant = callLinkStatus[0]; |
| |
| unsigned mandatoryMinimum; |
| if (FunctionExecutable* functionExecutable = callVariant.functionExecutable()) |
| mandatoryMinimum = functionExecutable->parameterCount(); |
| else |
| mandatoryMinimum = 0; |
| |
| // includes "this" |
| unsigned maxArgumentCountIncludingThis = std::max(callLinkStatus.maxArgumentCountIncludingThis(), mandatoryMinimum + 1); |
| |
| CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind); |
| if (inliningCost(callVariant, maxArgumentCountIncludingThis, kind) > getInliningBalance(callLinkStatus, specializationKind)) { |
| VERBOSE_LOG("Bailing inlining: inlining cost too high.\n"); |
| return false; |
| } |
| |
| int registerOffset = firstFreeReg; |
| registerOffset -= maxArgumentCountIncludingThis; |
| registerOffset -= CallFrame::headerSizeInRegisters; |
| registerOffset = -WTF::roundUpToMultipleOf(stackAlignmentRegisters(), -registerOffset); |
| |
| auto insertChecks = [&] (CodeBlock* codeBlock) { |
| emitFunctionChecks(callVariant, callTargetNode, thisArgument); |
| |
| int remappedRegisterOffset = |
| m_inlineStackTop->remapOperand(VirtualRegister(registerOffset)).virtualRegister().offset(); |
| |
| ensureLocals(VirtualRegister(remappedRegisterOffset).toLocal()); |
| |
| int argumentStart = registerOffset + CallFrame::headerSizeInRegisters; |
| int remappedArgumentStart = m_inlineStackTop->remapOperand(VirtualRegister(argumentStart)).virtualRegister().offset(); |
| |
| LoadVarargsData* data = m_graph.m_loadVarargsData.add(); |
| data->start = VirtualRegister(remappedArgumentStart + 1); |
| data->count = VirtualRegister(remappedRegisterOffset + CallFrameSlot::argumentCountIncludingThis); |
| data->offset = argumentsOffset; |
| data->limit = maxArgumentCountIncludingThis; |
| data->mandatoryMinimum = mandatoryMinimum; |
| |
| if (callOp == TailCallForwardVarargs) { |
| Node* argumentCount; |
| if (!inlineCallFrame()) |
| argumentCount = addToGraph(GetArgumentCountIncludingThis); |
| else if (inlineCallFrame()->isVarargs()) |
| argumentCount = getDirect(remapOperand(inlineCallFrame(), CallFrameSlot::argumentCountIncludingThis)); |
| else |
| argumentCount = addToGraph(JSConstant, OpInfo(m_graph.freeze(jsNumber(inlineCallFrame()->argumentCountIncludingThis)))); |
| addToGraph(ForwardVarargs, OpInfo(data), argumentCount); |
| } else { |
| Node* arguments = get(argumentsArgument); |
| auto argCountTmp = m_inlineStackTop->remapOperand(Operand::tmp(OpCallVarargs::argCountIncludingThis)); |
| setDirect(argCountTmp, addToGraph(VarargsLength, OpInfo(data), arguments)); |
| progressToNextCheckpoint(); |
| |
| addToGraph(LoadVarargs, OpInfo(data), getLocalOrTmp(argCountTmp), arguments); |
| } |
| |
| // LoadVarargs may OSR exit. Hence, we need to keep alive callTargetNode, thisArgument |
| // and argumentsArgument for the baseline JIT. However, we only need a Phantom for |
| // callTargetNode because the other 2 are still in use and alive at this point. |
| addToGraph(Phantom, callTargetNode); |
| |
| // In DFG IR before SSA, we cannot insert control flow between after the |
| // LoadVarargs and the last SetArgumentDefinitely. This isn't a problem once we get to DFG |
| // SSA. Fortunately, we also have other reasons for not inserting control flow |
| // before SSA. |
| |
| VariableAccessData* countVariable = newVariableAccessData(data->count); |
| // This is pretty lame, but it will force the count to be flushed as an int. This doesn't |
| // matter very much, since our use of a SetArgumentDefinitely and Flushes for this local slot is |
| // mostly just a formality. |
| countVariable->predict(SpecInt32Only); |
| countVariable->mergeIsProfitableToUnbox(true); |
| Node* setArgumentCount = addToGraph(SetArgumentDefinitely, OpInfo(countVariable)); |
| m_currentBlock->variablesAtTail.setOperand(countVariable->operand(), setArgumentCount); |
| |
| set(VirtualRegister(argumentStart), get(thisArgument), ImmediateNakedSet); |
| unsigned numSetArguments = 0; |
| for (unsigned argument = 1; argument < maxArgumentCountIncludingThis; ++argument) { |
| VariableAccessData* variable = newVariableAccessData(VirtualRegister(remappedArgumentStart + argument)); |
| variable->mergeShouldNeverUnbox(true); // We currently have nowhere to put the type check on the LoadVarargs. LoadVarargs is effectful, so after it finishes, we cannot exit. |
| |
| // For a while it had been my intention to do things like this inside the |
| // prediction injection phase. But in this case it's really best to do it here, |
| // because it's here that we have access to the variable access datas for the |
| // inlining we're about to do. |
| // |
| // Something else that's interesting here is that we'd really love to get |
| // predictions from the arguments loaded at the callsite, rather than the |
| // arguments received inside the callee. But that probably won't matter for most |
| // calls. |
| if (codeBlock && argument < static_cast<unsigned>(codeBlock->numParameters())) { |
| ConcurrentJSLocker locker(codeBlock->m_lock); |
| ValueProfile& profile = codeBlock->valueProfileForArgument(argument); |
| variable->predict(profile.computeUpdatedPrediction(locker)); |
| } |
| |
| Node* setArgument = addToGraph(numSetArguments >= mandatoryMinimum ? SetArgumentMaybe : SetArgumentDefinitely, OpInfo(variable)); |
| m_currentBlock->variablesAtTail.setOperand(variable->operand(), setArgument); |
| ++numSetArguments; |
| } |
| }; |
| |
| // Intrinsics and internal functions can only be inlined if we're not doing varargs. This is because |
| // we currently don't have any way of getting profiling information for arguments to non-JS varargs |
| // calls. The prediction propagator won't be of any help because LoadVarargs obscures the data flow, |
| // and there are no callsite value profiles and native function won't have callee value profiles for |
| // those arguments. Even worse, if the intrinsic decides to exit, it won't really have anywhere to |
| // exit to: LoadVarargs is effectful and it's part of the op_call_varargs, so we can't exit without |
| // calling LoadVarargs twice. |
| inlineCall(callTargetNode, result, callVariant, registerOffset, maxArgumentCountIncludingThis, kind, nullptr, insertChecks); |
| |
| |
| VERBOSE_LOG("Successful inlining (varargs, monomorphic).\nStack: ", currentCodeOrigin(), "\n"); |
| return true; |
| } |
| |
| unsigned ByteCodeParser::getInliningBalance(const CallLinkStatus& callLinkStatus, CodeSpecializationKind specializationKind) |
| { |
| unsigned inliningBalance = Options::maximumFunctionForCallInlineCandidateBytecodeCost(); |
| if (specializationKind == CodeForConstruct) |
| inliningBalance = std::min(inliningBalance, Options::maximumFunctionForConstructInlineCandidateBytecoodeCost()); |
| if (callLinkStatus.isClosureCall()) |
| inliningBalance = std::min(inliningBalance, Options::maximumFunctionForClosureCallInlineCandidateBytecodeCost()); |
| return inliningBalance; |
| } |
| |
| ByteCodeParser::CallOptimizationResult ByteCodeParser::handleInlining( |
| Node* callTargetNode, VirtualRegister result, const CallLinkStatus& callLinkStatus, |
| int registerOffset, VirtualRegister thisArgument, |
| int argumentCountIncludingThis, |
| BytecodeIndex nextIndex, NodeType callOp, InlineCallFrame::Kind kind, SpeculatedType prediction) |
| { |
| VERBOSE_LOG("Handling inlining...\nStack: ", currentCodeOrigin(), "\n"); |
| |
| CodeSpecializationKind specializationKind = InlineCallFrame::specializationKindFor(kind); |
| unsigned inliningBalance = getInliningBalance(callLinkStatus, specializationKind); |
| |
| // First check if we can avoid creating control flow. Our inliner does some CFG |
| // simplification on the fly and this helps reduce compile times, but we can only leverage |
| // this in cases where we don't need control flow diamonds to check the callee. |
| if (!callLinkStatus.couldTakeSlowPath() && callLinkStatus.size() == 1) { |
| return handleCallVariant( |
| callTargetNode, result, callLinkStatus[0], registerOffset, thisArgument, |
| argumentCountIncludingThis, nextIndex, kind, prediction, inliningBalance, nullptr, true); |
| } |
| |
| // We need to create some kind of switch over callee. For now we only do this if we believe that |
| // we're in the top tier. We have two reasons for this: first, it provides us an opportunity to |
| // do more detailed polyvariant/polymorphic profiling; and second, it reduces compile times in |
| // the DFG. And by polyvariant profiling we mean polyvariant profiling of *this* call. Note that |
| // we could improve that aspect of this by doing polymorphic inlining but having the profiling |
| // also. |
| if (!m_graph.m_plan.isFTL() || !Options::usePolymorphicCallInlining()) { |
| VERBOSE_LOG("Bailing inlining (hard).\nStack: ", currentCodeOrigin(), "\n"); |
| return CallOptimizationResult::DidNothing; |
| } |
| |
| // If the claim is that this did not originate from a stub, then we don't want to emit a switch |
| // statement. Whenever the non-stub profiling says that it could take slow path, it really means that |
| // it has no idea. |
| if (!Options::usePolymorphicCallInliningForNonStubStatus() |
| && !callLinkStatus.isBasedOnStub()) { |
| VERBOSE_LOG("Bailing inlining (non-stub polymorphism).\nStack: ", currentCodeOrigin(), "\n"); |
| return CallOptimizationResult::DidNothing; |
| } |
| |
| bool allAreClosureCalls = true; |
| bool allAreDirectCalls = true; |
| for (unsigned i = callLinkStatus.size(); i--;) { |
| if (callLinkStatus[i].isClosureCall()) |
| allAreDirectCalls = false; |
| else |
| allAreClosureCalls = false; |
| } |
| |
| Node* thingToSwitchOn; |
| if (allAreDirectCalls) |
| thingToSwitchOn = callTargetNode; |
| else if (allAreClosureCalls) |
| thingToSwitchOn = addToGraph(GetExecutable, callTargetNode); |
| else { |
| // FIXME: We should be able to handle this case, but it's tricky and we don't know of cases |
| // where it would be beneficial. It might be best to handle these cases as if all calls were |
| // closure calls. |
| // https://bugs.webkit.org/show_bug.cgi?id=136020 |
| VERBOSE_LOG("Bailing inlining (mix).\nStack: ", currentCodeOrigin(), "\n"); |
| return CallOptimizationResult::DidNothing; |
| } |
| |
| VERBOSE_LOG("Doing hard inlining...\nStack: ", currentCodeOrigin(), "\n"); |
| |
| // This makes me wish that we were in SSA all the time. We need to pick a variable into which to |
| // store the callee so that it will be accessible to all of the blocks we're about to create. We |
| // get away with doing an immediate-set here because we wouldn't have performed any side effects |
| // yet. |
| VERBOSE_LOG("Register offset: ", registerOffset); |
| VirtualRegister calleeReg(registerOffset + CallFrameSlot::callee); |
| calleeReg = m_inlineStackTop->remapOperand(calleeReg).virtualRegister(); |
| VERBOSE_LOG("Callee is going to be ", calleeReg, "\n"); |
| setDirect(calleeReg, callTargetNode, ImmediateSetWithFlush); |
| |
| // It's OK to exit right now, even though we set some locals. That's because those locals are not |
| // user-visible. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| SwitchData& data = *m_graph.m_switchData.add(); |
| data.kind = SwitchCell; |
| addToGraph(Switch, OpInfo(&data), thingToSwitchOn); |
| m_currentBlock->didLink(); |
| |
| BasicBlock* continuationBlock = allocateUntargetableBlock(); |
| VERBOSE_LOG("Adding untargetable block ", RawPointer(continuationBlock), " (continuation)\n"); |
| |
| // We may force this true if we give up on inlining any of the edges. |
| bool couldTakeSlowPath = callLinkStatus.couldTakeSlowPath(); |
| |
| VERBOSE_LOG("About to loop over functions at ", currentCodeOrigin(), ".\n"); |
| |
| BytecodeIndex oldIndex = m_currentIndex; |
| for (unsigned i = 0; i < callLinkStatus.size(); ++i) { |
| m_currentIndex = oldIndex; |
| BasicBlock* calleeEntryBlock = allocateUntargetableBlock(); |
| m_currentBlock = calleeEntryBlock; |
| prepareToParseBlock(); |
| |
| // At the top of each switch case, we can exit. |
| m_exitOK = true; |
| |
| Node* myCallTargetNode = getDirect(calleeReg); |
| |
| auto inliningResult = handleCallVariant( |
| myCallTargetNode, result, callLinkStatus[i], registerOffset, |
| thisArgument, argumentCountIncludingThis, nextIndex, kind, prediction, |
| inliningBalance, continuationBlock, false); |
| |
| if (inliningResult == CallOptimizationResult::DidNothing) { |
| // That failed so we let the block die. Nothing interesting should have been added to |
| // the block. We also give up on inlining any of the (less frequent) callees. |
| ASSERT(m_graph.m_blocks.last() == m_currentBlock); |
| m_graph.killBlockAndItsContents(m_currentBlock); |
| m_graph.m_blocks.removeLast(); |
| VERBOSE_LOG("Inlining of a poly call failed, we will have to go through a slow path\n"); |
| |
| // The fact that inlining failed means we need a slow path. |
| couldTakeSlowPath = true; |
| break; |
| } |
| |
| JSCell* thingToCaseOn; |
| if (allAreDirectCalls) |
| thingToCaseOn = callLinkStatus[i].nonExecutableCallee(); |
| else { |
| ASSERT(allAreClosureCalls); |
| thingToCaseOn = callLinkStatus[i].executable(); |
| } |
| data.cases.append(SwitchCase(m_graph.freeze(thingToCaseOn), calleeEntryBlock)); |
| VERBOSE_LOG("Finished optimizing ", callLinkStatus[i], " at ", currentCodeOrigin(), ".\n"); |
| } |
| |
| // Slow path block |
| m_currentBlock = allocateUntargetableBlock(); |
| m_currentIndex = oldIndex; |
| m_exitOK = true; |
| data.fallThrough = BranchTarget(m_currentBlock); |
| prepareToParseBlock(); |
| Node* myCallTargetNode = getDirect(calleeReg); |
| if (couldTakeSlowPath) { |
| addCall( |
| result, callOp, nullptr, myCallTargetNode, argumentCountIncludingThis, |
| registerOffset, prediction); |
| VERBOSE_LOG("We added a call in the slow path\n"); |
| } else { |
| addToGraph(CheckBadCell); |
| addToGraph(Phantom, myCallTargetNode); |
| emitArgumentPhantoms(registerOffset, argumentCountIncludingThis); |
| |
| if (result.isValid()) |
| set(result, addToGraph(BottomValue)); |
| VERBOSE_LOG("couldTakeSlowPath was false\n"); |
| } |
| |
| m_currentIndex = nextIndex; |
| m_exitOK = true; // Origin changed, so it's fine to exit again. |
| processSetLocalQueue(); |
| |
| if (Node* terminal = m_currentBlock->terminal()) |
| ASSERT_UNUSED(terminal, terminal->op() == TailCall || terminal->op() == TailCallVarargs || terminal->op() == TailCallForwardVarargs); |
| else { |
| addJumpTo(continuationBlock); |
| } |
| |
| prepareToParseBlock(); |
| |
| m_currentIndex = oldIndex; |
| m_currentBlock = continuationBlock; |
| m_exitOK = true; |
| |
| VERBOSE_LOG("Done inlining (hard).\nStack: ", currentCodeOrigin(), "\n"); |
| return CallOptimizationResult::Inlined; |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleMinMax(VirtualRegister result, NodeType op, int registerOffset, int argumentCountIncludingThis, const ChecksFunctor& insertChecks) |
| { |
| ASSERT(op == ArithMin || op == ArithMax); |
| |
| if (argumentCountIncludingThis == 1) { |
| insertChecks(); |
| double limit = op == ArithMax ? -std::numeric_limits<double>::infinity() : +std::numeric_limits<double>::infinity(); |
| set(result, addToGraph(JSConstant, OpInfo(m_graph.freeze(jsDoubleNumber(limit))))); |
| return true; |
| } |
| |
| if (argumentCountIncludingThis == 2) { |
| insertChecks(); |
| Node* resultNode = get(VirtualRegister(virtualRegisterForArgument(1, registerOffset))); |
| addToGraph(Phantom, Edge(resultNode, NumberUse)); |
| set(result, resultNode); |
| return true; |
| } |
| |
| if (argumentCountIncludingThis == 3) { |
| insertChecks(); |
| set(result, addToGraph(op, get(virtualRegisterForArgument(1, registerOffset)), get(virtualRegisterForArgument(2, registerOffset)))); |
| return true; |
| } |
| |
| // Don't handle >=3 arguments for now. |
| return false; |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleIntrinsicCall(Node* callee, VirtualRegister result, Intrinsic intrinsic, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, const ChecksFunctor& insertChecks) |
| { |
| VERBOSE_LOG(" The intrinsic is ", intrinsic, "\n"); |
| |
| if (!isOpcodeShape<OpCallShape>(m_currentInstruction)) |
| return false; |
| |
| // It so happens that the code below doesn't handle the invalid result case. We could fix that, but |
| // it would only benefit intrinsics called as setters, like if you do: |
| // |
| // o.__defineSetter__("foo", Math.pow) |
| // |
| // Which is extremely amusing, but probably not worth optimizing. |
| if (!result.isValid()) |
| return false; |
| |
| bool didSetResult = false; |
| auto setResult = [&] (Node* node) { |
| RELEASE_ASSERT(!didSetResult); |
| set(result, node); |
| didSetResult = true; |
| }; |
| |
| auto inlineIntrinsic = [&] { |
| switch (intrinsic) { |
| |
| // Intrinsic Functions: |
| |
| case AbsIntrinsic: { |
| if (argumentCountIncludingThis == 1) { // Math.abs() |
| insertChecks(); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantNaN))); |
| return true; |
| } |
| |
| if (!MacroAssembler::supportsFloatingPointAbs()) |
| return false; |
| |
| insertChecks(); |
| Node* node = addToGraph(ArithAbs, get(virtualRegisterForArgument(1, registerOffset))); |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Overflow)) |
| node->mergeFlags(NodeMayOverflowInt32InDFG); |
| setResult(node); |
| return true; |
| } |
| |
| case MinIntrinsic: |
| case MaxIntrinsic: |
| if (handleMinMax(result, intrinsic == MinIntrinsic ? ArithMin : ArithMax, registerOffset, argumentCountIncludingThis, insertChecks)) { |
| didSetResult = true; |
| return true; |
| } |
| return false; |
| |
| #define DFG_ARITH_UNARY(capitalizedName, lowerName) \ |
| case capitalizedName##Intrinsic: |
| FOR_EACH_DFG_ARITH_UNARY_OP(DFG_ARITH_UNARY) |
| #undef DFG_ARITH_UNARY |
| { |
| if (argumentCountIncludingThis == 1) { |
| insertChecks(); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantNaN))); |
| return true; |
| } |
| Arith::UnaryType type = Arith::UnaryType::Sin; |
| switch (intrinsic) { |
| #define DFG_ARITH_UNARY(capitalizedName, lowerName) \ |
| case capitalizedName##Intrinsic: \ |
| type = Arith::UnaryType::capitalizedName; \ |
| break; |
| FOR_EACH_DFG_ARITH_UNARY_OP(DFG_ARITH_UNARY) |
| #undef DFG_ARITH_UNARY |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| insertChecks(); |
| setResult(addToGraph(ArithUnary, OpInfo(static_cast<std::underlying_type<Arith::UnaryType>::type>(type)), get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case FRoundIntrinsic: |
| case SqrtIntrinsic: { |
| if (argumentCountIncludingThis == 1) { |
| insertChecks(); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantNaN))); |
| return true; |
| } |
| |
| NodeType nodeType = Unreachable; |
| switch (intrinsic) { |
| case FRoundIntrinsic: |
| nodeType = ArithFRound; |
| break; |
| case SqrtIntrinsic: |
| nodeType = ArithSqrt; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| insertChecks(); |
| setResult(addToGraph(nodeType, get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case PowIntrinsic: { |
| if (argumentCountIncludingThis < 3) { |
| // Math.pow() and Math.pow(x) return NaN. |
| insertChecks(); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantNaN))); |
| return true; |
| } |
| insertChecks(); |
| VirtualRegister xOperand = virtualRegisterForArgument(1, registerOffset); |
| VirtualRegister yOperand = virtualRegisterForArgument(2, registerOffset); |
| setResult(addToGraph(ArithPow, get(xOperand), get(yOperand))); |
| return true; |
| } |
| |
| case TypedArrayEntriesIntrinsic: |
| case TypedArrayKeysIntrinsic: |
| case TypedArrayValuesIntrinsic: { |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| ArrayMode mode = getArrayMode(Array::Read); |
| if (!mode.isSomeTypedArrayView()) |
| return false; |
| |
| addToGraph(CheckArray, OpInfo(mode.asWord()), get(virtualRegisterForArgument(0, registerOffset))); |
| addToGraph(CheckNeutered, get(virtualRegisterForArgument(0, registerOffset))); |
| FALLTHROUGH; |
| } |
| |
| case ArrayEntriesIntrinsic: |
| case ArrayKeysIntrinsic: |
| case ArrayValuesIntrinsic: { |
| insertChecks(); |
| |
| IterationKind kind; |
| switch (intrinsic) { |
| case ArrayValuesIntrinsic: |
| case TypedArrayValuesIntrinsic: |
| kind = IterationKind::Values; |
| break; |
| case ArrayKeysIntrinsic: |
| case TypedArrayKeysIntrinsic: |
| kind = IterationKind::Keys; |
| break; |
| case ArrayEntriesIntrinsic: |
| case TypedArrayEntriesIntrinsic: |
| kind = IterationKind::Entries; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| // Add the constant before exit becomes invalid because we may want to insert (redundant) checks on it in Fixup. |
| Node* kindNode = jsConstant(jsNumber(static_cast<uint32_t>(kind))); |
| |
| // We don't have an existing error string. |
| unsigned errorStringIndex = UINT32_MAX; |
| Node* object = addToGraph(ToObject, OpInfo(errorStringIndex), OpInfo(SpecNone), get(virtualRegisterForArgument(0, registerOffset))); |
| |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| Node* iterator = addToGraph(NewArrayIterator, OpInfo(m_graph.registerStructure(globalObject->arrayIteratorStructure()))); |
| |
| addToGraph(PutInternalField, OpInfo(static_cast<uint32_t>(JSArrayIterator::Field::IteratedObject)), iterator, object); |
| addToGraph(PutInternalField, OpInfo(static_cast<uint32_t>(JSArrayIterator::Field::Kind)), iterator, kindNode); |
| |
| setResult(iterator); |
| return true; |
| } |
| |
| case ArrayPushIntrinsic: { |
| if (static_cast<unsigned>(argumentCountIncludingThis) >= MIN_SPARSE_ARRAY_INDEX) |
| return false; |
| |
| ArrayMode arrayMode = getArrayMode(Array::Write); |
| if (!arrayMode.isJSArray()) |
| return false; |
| switch (arrayMode.type()) { |
| case Array::Int32: |
| case Array::Double: |
| case Array::Contiguous: |
| case Array::ArrayStorage: { |
| insertChecks(); |
| |
| addVarArgChild(nullptr); // For storage. |
| for (int i = 0; i < argumentCountIncludingThis; ++i) |
| addVarArgChild(get(virtualRegisterForArgument(i, registerOffset))); |
| Node* arrayPush = addToGraph(Node::VarArg, ArrayPush, OpInfo(arrayMode.asWord()), OpInfo(prediction)); |
| setResult(arrayPush); |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| } |
| |
| case ArraySliceIntrinsic: { |
| if (argumentCountIncludingThis < 1) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantCache) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache)) |
| return false; |
| |
| ArrayMode arrayMode = getArrayMode(Array::Read); |
| if (!arrayMode.isJSArray()) |
| return false; |
| |
| if (!arrayMode.isJSArrayWithOriginalStructure()) |
| return false; |
| |
| switch (arrayMode.type()) { |
| case Array::Double: |
| case Array::Int32: |
| case Array::Contiguous: { |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| |
| Structure* arrayPrototypeStructure = globalObject->arrayPrototype()->structure(*m_vm); |
| Structure* objectPrototypeStructure = globalObject->objectPrototype()->structure(*m_vm); |
| |
| // FIXME: We could easily relax the Array/Object.prototype transition as long as we OSR exitted if we saw a hole. |
| // https://bugs.webkit.org/show_bug.cgi?id=173171 |
| if (globalObject->arraySpeciesWatchpointSet().state() == IsWatched |
| && globalObject->havingABadTimeWatchpoint()->isStillValid() |
| && arrayPrototypeStructure->transitionWatchpointSetIsStillValid() |
| && objectPrototypeStructure->transitionWatchpointSetIsStillValid() |
| && globalObject->arrayPrototypeChainIsSane()) { |
| |
| m_graph.watchpoints().addLazily(globalObject->arraySpeciesWatchpointSet()); |
| m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint()); |
| m_graph.registerAndWatchStructureTransition(arrayPrototypeStructure); |
| m_graph.registerAndWatchStructureTransition(objectPrototypeStructure); |
| |
| insertChecks(); |
| |
| Node* array = get(virtualRegisterForArgument(0, registerOffset)); |
| // We do a few things here to prove that we aren't skipping doing side-effects in an observable way: |
| // 1. We ensure that the "constructor" property hasn't been changed (because the observable |
| // effects of slice require that we perform a Get(array, "constructor") and we can skip |
| // that if we're an original array structure. (We can relax this in the future by using |
| // TryGetById and CheckCell). |
| // |
| // 2. We check that the array we're calling slice on has the same global object as the lexical |
| // global object that this code is running in. This requirement is necessary because we setup the |
| // watchpoints above on the lexical global object. This means that code that calls slice on |
| // arrays produced by other global objects won't get this optimization. We could relax this |
| // requirement in the future by checking that the watchpoint hasn't fired at runtime in the code |
| // we generate instead of registering it as a watchpoint that would invalidate the compilation. |
| // |
| // 3. By proving we're an original array structure, we guarantee that the incoming array |
| // isn't a subclass of Array. |
| |
| StructureSet structureSet; |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(ArrayWithInt32)); |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous)); |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(ArrayWithDouble)); |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithInt32)); |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous)); |
| structureSet.add(globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithDouble)); |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(structureSet)), array); |
| |
| addVarArgChild(array); |
| if (argumentCountIncludingThis >= 2) |
| addVarArgChild(get(virtualRegisterForArgument(1, registerOffset))); // Start index. |
| if (argumentCountIncludingThis >= 3) |
| addVarArgChild(get(virtualRegisterForArgument(2, registerOffset))); // End index. |
| addVarArgChild(addToGraph(GetButterfly, array)); |
| |
| Node* arraySlice = addToGraph(Node::VarArg, ArraySlice, OpInfo(), OpInfo()); |
| setResult(arraySlice); |
| return true; |
| } |
| |
| return false; |
| } |
| default: |
| return false; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| case ArrayIndexOfIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantCache) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| ArrayMode arrayMode = getArrayMode(Array::Read); |
| if (!arrayMode.isJSArray()) |
| return false; |
| |
| if (!arrayMode.isJSArrayWithOriginalStructure()) |
| return false; |
| |
| // We do not want to convert arrays into one type just to perform indexOf. |
| if (arrayMode.doesConversion()) |
| return false; |
| |
| switch (arrayMode.type()) { |
| case Array::Double: |
| case Array::Int32: |
| case Array::Contiguous: { |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| |
| Structure* arrayPrototypeStructure = globalObject->arrayPrototype()->structure(*m_vm); |
| Structure* objectPrototypeStructure = globalObject->objectPrototype()->structure(*m_vm); |
| |
| // FIXME: We could easily relax the Array/Object.prototype transition as long as we OSR exitted if we saw a hole. |
| // https://bugs.webkit.org/show_bug.cgi?id=173171 |
| if (arrayPrototypeStructure->transitionWatchpointSetIsStillValid() |
| && objectPrototypeStructure->transitionWatchpointSetIsStillValid() |
| && globalObject->arrayPrototypeChainIsSane()) { |
| |
| m_graph.registerAndWatchStructureTransition(arrayPrototypeStructure); |
| m_graph.registerAndWatchStructureTransition(objectPrototypeStructure); |
| |
| insertChecks(); |
| |
| Node* array = get(virtualRegisterForArgument(0, registerOffset)); |
| addVarArgChild(array); |
| addVarArgChild(get(virtualRegisterForArgument(1, registerOffset))); // Search element. |
| if (argumentCountIncludingThis >= 3) |
| addVarArgChild(get(virtualRegisterForArgument(2, registerOffset))); // Start index. |
| addVarArgChild(nullptr); |
| |
| Node* node = addToGraph(Node::VarArg, ArrayIndexOf, OpInfo(arrayMode.asWord()), OpInfo()); |
| setResult(node); |
| return true; |
| } |
| |
| return false; |
| } |
| default: |
| return false; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return false; |
| |
| } |
| |
| case ArrayPopIntrinsic: { |
| ArrayMode arrayMode = getArrayMode(Array::Write); |
| if (!arrayMode.isJSArray()) |
| return false; |
| switch (arrayMode.type()) { |
| case Array::Int32: |
| case Array::Double: |
| case Array::Contiguous: |
| case Array::ArrayStorage: { |
| insertChecks(); |
| Node* arrayPop = addToGraph(ArrayPop, OpInfo(arrayMode.asWord()), OpInfo(prediction), get(virtualRegisterForArgument(0, registerOffset))); |
| setResult(arrayPop); |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| } |
| |
| case AtomicsAddIntrinsic: |
| case AtomicsAndIntrinsic: |
| case AtomicsCompareExchangeIntrinsic: |
| case AtomicsExchangeIntrinsic: |
| case AtomicsIsLockFreeIntrinsic: |
| case AtomicsLoadIntrinsic: |
| case AtomicsOrIntrinsic: |
| case AtomicsStoreIntrinsic: |
| case AtomicsSubIntrinsic: |
| case AtomicsXorIntrinsic: { |
| if (!is64Bit()) |
| return false; |
| |
| NodeType op = LastNodeType; |
| Array::Action action = Array::Write; |
| unsigned numArgs = 0; // Number of actual args; we add one for the backing store pointer. |
| switch (intrinsic) { |
| case AtomicsAddIntrinsic: |
| op = AtomicsAdd; |
| numArgs = 3; |
| break; |
| case AtomicsAndIntrinsic: |
| op = AtomicsAnd; |
| numArgs = 3; |
| break; |
| case AtomicsCompareExchangeIntrinsic: |
| op = AtomicsCompareExchange; |
| numArgs = 4; |
| break; |
| case AtomicsExchangeIntrinsic: |
| op = AtomicsExchange; |
| numArgs = 3; |
| break; |
| case AtomicsIsLockFreeIntrinsic: |
| // This gets no backing store, but we need no special logic for this since this also does |
| // not need varargs. |
| op = AtomicsIsLockFree; |
| numArgs = 1; |
| break; |
| case AtomicsLoadIntrinsic: |
| op = AtomicsLoad; |
| numArgs = 2; |
| action = Array::Read; |
| break; |
| case AtomicsOrIntrinsic: |
| op = AtomicsOr; |
| numArgs = 3; |
| break; |
| case AtomicsStoreIntrinsic: |
| op = AtomicsStore; |
| numArgs = 3; |
| break; |
| case AtomicsSubIntrinsic: |
| op = AtomicsSub; |
| numArgs = 3; |
| break; |
| case AtomicsXorIntrinsic: |
| op = AtomicsXor; |
| numArgs = 3; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| if (static_cast<unsigned>(argumentCountIncludingThis) < 1 + numArgs) |
| return false; |
| |
| insertChecks(); |
| |
| Vector<Node*, 3> args; |
| for (unsigned i = 0; i < numArgs; ++i) |
| args.append(get(virtualRegisterForArgument(1 + i, registerOffset))); |
| |
| Node* resultNode; |
| if (numArgs + 1 <= 3) { |
| while (args.size() < 3) |
| args.append(nullptr); |
| resultNode = addToGraph(op, OpInfo(ArrayMode(Array::SelectUsingPredictions, action).asWord()), OpInfo(prediction), args[0], args[1], args[2]); |
| } else { |
| for (Node* node : args) |
| addVarArgChild(node); |
| addVarArgChild(nullptr); |
| resultNode = addToGraph(Node::VarArg, op, OpInfo(ArrayMode(Array::SelectUsingPredictions, action).asWord()), OpInfo(prediction)); |
| } |
| |
| setResult(resultNode); |
| return true; |
| } |
| |
| case ParseIntIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell) || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| VirtualRegister valueOperand = virtualRegisterForArgument(1, registerOffset); |
| Node* parseInt; |
| if (argumentCountIncludingThis == 2) |
| parseInt = addToGraph(ParseInt, OpInfo(), OpInfo(prediction), get(valueOperand)); |
| else { |
| ASSERT(argumentCountIncludingThis > 2); |
| VirtualRegister radixOperand = virtualRegisterForArgument(2, registerOffset); |
| parseInt = addToGraph(ParseInt, OpInfo(), OpInfo(prediction), get(valueOperand), get(radixOperand)); |
| } |
| setResult(parseInt); |
| return true; |
| } |
| |
| case CharCodeAtIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Uncountable) || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| VirtualRegister thisOperand = virtualRegisterForArgument(0, registerOffset); |
| VirtualRegister indexOperand = virtualRegisterForArgument(1, registerOffset); |
| Node* charCode = addToGraph(StringCharCodeAt, OpInfo(ArrayMode(Array::String, Array::Read).asWord()), get(thisOperand), get(indexOperand)); |
| |
| setResult(charCode); |
| return true; |
| } |
| |
| case StringPrototypeCodePointAtIntrinsic: { |
| if (!is64Bit()) |
| return false; |
| |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, Uncountable) || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| VirtualRegister thisOperand = virtualRegisterForArgument(0, registerOffset); |
| VirtualRegister indexOperand = virtualRegisterForArgument(1, registerOffset); |
| Node* result = addToGraph(StringCodePointAt, OpInfo(ArrayMode(Array::String, Array::Read).asWord()), get(thisOperand), get(indexOperand)); |
| |
| setResult(result); |
| return true; |
| } |
| |
| case CharAtIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| // FIXME: String#charAt returns empty string when index is out-of-bounds, and this does not break the AI's claim. |
| // Only FTL supports out-of-bounds version now. We should support out-of-bounds version even in DFG. |
| // https://bugs.webkit.org/show_bug.cgi?id=201678 |
| |
| insertChecks(); |
| VirtualRegister thisOperand = virtualRegisterForArgument(0, registerOffset); |
| VirtualRegister indexOperand = virtualRegisterForArgument(1, registerOffset); |
| Node* charCode = addToGraph(StringCharAt, OpInfo(ArrayMode(Array::String, Array::Read).asWord()), get(thisOperand), get(indexOperand)); |
| |
| setResult(charCode); |
| return true; |
| } |
| case Clz32Intrinsic: { |
| insertChecks(); |
| if (argumentCountIncludingThis == 1) |
| setResult(addToGraph(JSConstant, OpInfo(m_graph.freeze(jsNumber(32))))); |
| else { |
| Node* operand = get(virtualRegisterForArgument(1, registerOffset)); |
| setResult(addToGraph(ArithClz32, operand)); |
| } |
| return true; |
| } |
| case FromCharCodeIntrinsic: { |
| if (argumentCountIncludingThis != 2) |
| return false; |
| |
| insertChecks(); |
| VirtualRegister indexOperand = virtualRegisterForArgument(1, registerOffset); |
| Node* charCode = addToGraph(StringFromCharCode, get(indexOperand)); |
| |
| setResult(charCode); |
| |
| return true; |
| } |
| |
| case RegExpExecIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| Node* regExpExec = addToGraph(RegExpExec, OpInfo(0), OpInfo(prediction), addToGraph(GetGlobalObject, callee), get(virtualRegisterForArgument(0, registerOffset)), get(virtualRegisterForArgument(1, registerOffset))); |
| setResult(regExpExec); |
| |
| return true; |
| } |
| |
| case RegExpTestIntrinsic: |
| case RegExpTestFastIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (intrinsic == RegExpTestIntrinsic) { |
| // Don't inline intrinsic if we exited due to one of the primordial RegExp checks failing. |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) |
| return false; |
| |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| Structure* regExpStructure = globalObject->regExpStructure(); |
| m_graph.registerStructure(regExpStructure); |
| ASSERT(regExpStructure->storedPrototype().isObject()); |
| ASSERT(regExpStructure->storedPrototype().asCell()->classInfo(*m_vm) == RegExpPrototype::info()); |
| |
| FrozenValue* regExpPrototypeObjectValue = m_graph.freeze(regExpStructure->storedPrototype()); |
| Structure* regExpPrototypeStructure = regExpPrototypeObjectValue->structure(); |
| |
| auto isRegExpPropertySame = [&] (JSValue primordialProperty, UniquedStringImpl* propertyUID) { |
| JSValue currentProperty; |
| if (!m_graph.getRegExpPrototypeProperty(regExpStructure->storedPrototypeObject(), regExpPrototypeStructure, propertyUID, currentProperty)) |
| return false; |
| |
| return currentProperty == primordialProperty; |
| }; |
| |
| // Check that RegExp.exec is still the primordial RegExp.prototype.exec |
| if (!isRegExpPropertySame(globalObject->regExpProtoExecFunction(), m_vm->propertyNames->exec.impl())) |
| return false; |
| |
| // Check that regExpObject is actually a RegExp object. |
| Node* regExpObject = get(virtualRegisterForArgument(0, registerOffset)); |
| addToGraph(Check, Edge(regExpObject, RegExpObjectUse)); |
| |
| // Check that regExpObject's exec is actually the primodial RegExp.prototype.exec. |
| UniquedStringImpl* execPropertyID = m_vm->propertyNames->exec.impl(); |
| unsigned execIndex = m_graph.identifiers().ensure(execPropertyID); |
| Node* actualProperty = addToGraph(TryGetById, OpInfo(execIndex), OpInfo(SpecFunction), Edge(regExpObject, CellUse)); |
| FrozenValue* regExpPrototypeExec = m_graph.freeze(globalObject->regExpProtoExecFunction()); |
| addToGraph(CheckCell, OpInfo(regExpPrototypeExec), Edge(actualProperty, CellUse)); |
| } |
| |
| insertChecks(); |
| Node* regExpObject = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* regExpExec = addToGraph(RegExpTest, OpInfo(0), OpInfo(prediction), addToGraph(GetGlobalObject, callee), regExpObject, get(virtualRegisterForArgument(1, registerOffset))); |
| setResult(regExpExec); |
| |
| return true; |
| } |
| |
| case RegExpMatchFastIntrinsic: { |
| RELEASE_ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| Node* regExpMatch = addToGraph(RegExpMatchFast, OpInfo(0), OpInfo(prediction), addToGraph(GetGlobalObject, callee), get(virtualRegisterForArgument(0, registerOffset)), get(virtualRegisterForArgument(1, registerOffset))); |
| setResult(regExpMatch); |
| return true; |
| } |
| |
| case ObjectCreateIntrinsic: { |
| if (argumentCountIncludingThis != 2) |
| return false; |
| |
| insertChecks(); |
| setResult(addToGraph(ObjectCreate, get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case ObjectGetPrototypeOfIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| setResult(addToGraph(GetPrototypeOf, OpInfo(0), OpInfo(prediction), get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case ObjectIsIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| insertChecks(); |
| setResult(addToGraph(SameValue, get(virtualRegisterForArgument(1, registerOffset)), get(virtualRegisterForArgument(2, registerOffset)))); |
| return true; |
| } |
| |
| case ObjectKeysIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| setResult(addToGraph(ObjectKeys, get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case ReflectGetPrototypeOfIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| setResult(addToGraph(GetPrototypeOf, OpInfo(0), OpInfo(prediction), Edge(get(virtualRegisterForArgument(1, registerOffset)), ObjectUse))); |
| return true; |
| } |
| |
| case IsTypedArrayViewIntrinsic: { |
| ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| setResult(addToGraph(IsTypedArrayView, OpInfo(prediction), get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| case StringPrototypeValueOfIntrinsic: { |
| insertChecks(); |
| Node* value = get(virtualRegisterForArgument(0, registerOffset)); |
| setResult(addToGraph(StringValueOf, value)); |
| return true; |
| } |
| |
| case StringPrototypeReplaceIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| // Don't inline intrinsic if we exited due to "search" not being a RegExp or String object. |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| // Don't inline intrinsic if we exited due to one of the primordial RegExp checks failing. |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) |
| return false; |
| |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| Structure* regExpStructure = globalObject->regExpStructure(); |
| m_graph.registerStructure(regExpStructure); |
| ASSERT(regExpStructure->storedPrototype().isObject()); |
| ASSERT(regExpStructure->storedPrototype().asCell()->classInfo(*m_vm) == RegExpPrototype::info()); |
| |
| FrozenValue* regExpPrototypeObjectValue = m_graph.freeze(regExpStructure->storedPrototype()); |
| Structure* regExpPrototypeStructure = regExpPrototypeObjectValue->structure(); |
| |
| auto isRegExpPropertySame = [&] (JSValue primordialProperty, UniquedStringImpl* propertyUID) { |
| JSValue currentProperty; |
| if (!m_graph.getRegExpPrototypeProperty(regExpStructure->storedPrototypeObject(), regExpPrototypeStructure, propertyUID, currentProperty)) |
| return false; |
| |
| return currentProperty == primordialProperty; |
| }; |
| |
| // Check that searchRegExp.exec is still the primordial RegExp.prototype.exec |
| if (!isRegExpPropertySame(globalObject->regExpProtoExecFunction(), m_vm->propertyNames->exec.impl())) |
| return false; |
| |
| // Check that searchRegExp.global is still the primordial RegExp.prototype.global |
| if (!isRegExpPropertySame(globalObject->regExpProtoGlobalGetter(), m_vm->propertyNames->global.impl())) |
| return false; |
| |
| // Check that searchRegExp.unicode is still the primordial RegExp.prototype.unicode |
| if (!isRegExpPropertySame(globalObject->regExpProtoUnicodeGetter(), m_vm->propertyNames->unicode.impl())) |
| return false; |
| |
| // Check that searchRegExp[Symbol.match] is still the primordial RegExp.prototype[Symbol.replace] |
| if (!isRegExpPropertySame(globalObject->regExpProtoSymbolReplaceFunction(), m_vm->propertyNames->replaceSymbol.impl())) |
| return false; |
| |
| insertChecks(); |
| |
| Node* resultNode = addToGraph(StringReplace, OpInfo(0), OpInfo(prediction), get(virtualRegisterForArgument(0, registerOffset)), get(virtualRegisterForArgument(1, registerOffset)), get(virtualRegisterForArgument(2, registerOffset))); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case StringPrototypeReplaceRegExpIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| insertChecks(); |
| Node* resultNode = addToGraph(StringReplaceRegExp, OpInfo(0), OpInfo(prediction), get(virtualRegisterForArgument(0, registerOffset)), get(virtualRegisterForArgument(1, registerOffset)), get(virtualRegisterForArgument(2, registerOffset))); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case RoundIntrinsic: |
| case FloorIntrinsic: |
| case CeilIntrinsic: |
| case TruncIntrinsic: { |
| if (argumentCountIncludingThis == 1) { |
| insertChecks(); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantNaN))); |
| return true; |
| } |
| insertChecks(); |
| Node* operand = get(virtualRegisterForArgument(1, registerOffset)); |
| NodeType op; |
| if (intrinsic == RoundIntrinsic) |
| op = ArithRound; |
| else if (intrinsic == FloorIntrinsic) |
| op = ArithFloor; |
| else if (intrinsic == CeilIntrinsic) |
| op = ArithCeil; |
| else { |
| ASSERT(intrinsic == TruncIntrinsic); |
| op = ArithTrunc; |
| } |
| Node* roundNode = addToGraph(op, OpInfo(0), OpInfo(prediction), operand); |
| setResult(roundNode); |
| return true; |
| } |
| case IMulIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| insertChecks(); |
| VirtualRegister leftOperand = virtualRegisterForArgument(1, registerOffset); |
| VirtualRegister rightOperand = virtualRegisterForArgument(2, registerOffset); |
| Node* left = get(leftOperand); |
| Node* right = get(rightOperand); |
| setResult(addToGraph(ArithIMul, left, right)); |
| return true; |
| } |
| |
| case RandomIntrinsic: { |
| insertChecks(); |
| setResult(addToGraph(ArithRandom)); |
| return true; |
| } |
| |
| case DFGTrueIntrinsic: { |
| insertChecks(); |
| setResult(jsConstant(jsBoolean(true))); |
| return true; |
| } |
| |
| case FTLTrueIntrinsic: { |
| insertChecks(); |
| setResult(jsConstant(jsBoolean(m_graph.m_plan.isFTL()))); |
| return true; |
| } |
| |
| case OSRExitIntrinsic: { |
| insertChecks(); |
| addToGraph(ForceOSRExit); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantUndefined))); |
| return true; |
| } |
| |
| case IsFinalTierIntrinsic: { |
| insertChecks(); |
| setResult(jsConstant(jsBoolean(Options::useFTLJIT() ? m_graph.m_plan.isFTL() : true))); |
| return true; |
| } |
| |
| case SetInt32HeapPredictionIntrinsic: { |
| insertChecks(); |
| for (int i = 1; i < argumentCountIncludingThis; ++i) { |
| Node* node = get(virtualRegisterForArgument(i, registerOffset)); |
| if (node->hasHeapPrediction()) |
| node->setHeapPrediction(SpecInt32Only); |
| } |
| setResult(addToGraph(JSConstant, OpInfo(m_constantUndefined))); |
| return true; |
| } |
| |
| case CheckInt32Intrinsic: { |
| insertChecks(); |
| for (int i = 1; i < argumentCountIncludingThis; ++i) { |
| Node* node = get(virtualRegisterForArgument(i, registerOffset)); |
| addToGraph(Phantom, Edge(node, Int32Use)); |
| } |
| setResult(jsConstant(jsBoolean(true))); |
| return true; |
| } |
| |
| case FiatInt52Intrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| insertChecks(); |
| VirtualRegister operand = virtualRegisterForArgument(1, registerOffset); |
| if (enableInt52()) |
| setResult(addToGraph(FiatInt52, get(operand))); |
| else |
| setResult(get(operand)); |
| return true; |
| } |
| |
| case JSMapGetIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| Node* map = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* normalizedKey = addToGraph(NormalizeMapKey, key); |
| Node* hash = addToGraph(MapHash, normalizedKey); |
| Node* bucket = addToGraph(GetMapBucket, Edge(map, MapObjectUse), Edge(normalizedKey), Edge(hash)); |
| Node* resultNode = addToGraph(LoadValueFromMapBucket, OpInfo(BucketOwnerType::Map), OpInfo(prediction), bucket); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSSetHasIntrinsic: |
| case JSMapHasIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| Node* mapOrSet = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* normalizedKey = addToGraph(NormalizeMapKey, key); |
| Node* hash = addToGraph(MapHash, normalizedKey); |
| UseKind useKind = intrinsic == JSSetHasIntrinsic ? SetObjectUse : MapObjectUse; |
| Node* bucket = addToGraph(GetMapBucket, OpInfo(0), Edge(mapOrSet, useKind), Edge(normalizedKey), Edge(hash)); |
| JSCell* sentinel = nullptr; |
| if (intrinsic == JSMapHasIntrinsic) |
| sentinel = m_vm->sentinelMapBucket(); |
| else |
| sentinel = m_vm->sentinelSetBucket(); |
| |
| FrozenValue* frozenPointer = m_graph.freeze(sentinel); |
| Node* invertedResult = addToGraph(CompareEqPtr, OpInfo(frozenPointer), bucket); |
| Node* resultNode = addToGraph(LogicalNot, invertedResult); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSSetAddIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* normalizedKey = addToGraph(NormalizeMapKey, key); |
| Node* hash = addToGraph(MapHash, normalizedKey); |
| addToGraph(SetAdd, base, normalizedKey, hash); |
| setResult(base); |
| return true; |
| } |
| |
| case JSMapSetIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* value = get(virtualRegisterForArgument(2, registerOffset)); |
| |
| Node* normalizedKey = addToGraph(NormalizeMapKey, key); |
| Node* hash = addToGraph(MapHash, normalizedKey); |
| |
| addVarArgChild(base); |
| addVarArgChild(normalizedKey); |
| addVarArgChild(value); |
| addVarArgChild(hash); |
| addToGraph(Node::VarArg, MapSet, OpInfo(0), OpInfo(0)); |
| setResult(base); |
| return true; |
| } |
| |
| case JSSetBucketHeadIntrinsic: |
| case JSMapBucketHeadIntrinsic: { |
| ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| Node* map = get(virtualRegisterForArgument(1, registerOffset)); |
| UseKind useKind = intrinsic == JSSetBucketHeadIntrinsic ? SetObjectUse : MapObjectUse; |
| Node* resultNode = addToGraph(GetMapBucketHead, Edge(map, useKind)); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSSetBucketNextIntrinsic: |
| case JSMapBucketNextIntrinsic: { |
| ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| Node* bucket = get(virtualRegisterForArgument(1, registerOffset)); |
| BucketOwnerType type = intrinsic == JSSetBucketNextIntrinsic ? BucketOwnerType::Set : BucketOwnerType::Map; |
| Node* resultNode = addToGraph(GetMapBucketNext, OpInfo(type), bucket); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSSetBucketKeyIntrinsic: |
| case JSMapBucketKeyIntrinsic: { |
| ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| Node* bucket = get(virtualRegisterForArgument(1, registerOffset)); |
| BucketOwnerType type = intrinsic == JSSetBucketKeyIntrinsic ? BucketOwnerType::Set : BucketOwnerType::Map; |
| Node* resultNode = addToGraph(LoadKeyFromMapBucket, OpInfo(type), OpInfo(prediction), bucket); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSMapBucketValueIntrinsic: { |
| ASSERT(argumentCountIncludingThis == 2); |
| |
| insertChecks(); |
| Node* bucket = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* resultNode = addToGraph(LoadValueFromMapBucket, OpInfo(BucketOwnerType::Map), OpInfo(prediction), bucket); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSWeakMapGetIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* map = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| addToGraph(Check, Edge(key, ObjectUse)); |
| Node* hash = addToGraph(MapHash, key); |
| Node* holder = addToGraph(WeakMapGet, Edge(map, WeakMapObjectUse), Edge(key, ObjectUse), Edge(hash, Int32Use)); |
| Node* resultNode = addToGraph(ExtractValueFromWeakMapGet, OpInfo(), OpInfo(prediction), holder); |
| |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSWeakMapHasIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* map = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| addToGraph(Check, Edge(key, ObjectUse)); |
| Node* hash = addToGraph(MapHash, key); |
| Node* holder = addToGraph(WeakMapGet, Edge(map, WeakMapObjectUse), Edge(key, ObjectUse), Edge(hash, Int32Use)); |
| Node* invertedResult = addToGraph(IsEmpty, holder); |
| Node* resultNode = addToGraph(LogicalNot, invertedResult); |
| |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSWeakSetHasIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* map = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| addToGraph(Check, Edge(key, ObjectUse)); |
| Node* hash = addToGraph(MapHash, key); |
| Node* holder = addToGraph(WeakMapGet, Edge(map, WeakSetObjectUse), Edge(key, ObjectUse), Edge(hash, Int32Use)); |
| Node* invertedResult = addToGraph(IsEmpty, holder); |
| Node* resultNode = addToGraph(LogicalNot, invertedResult); |
| |
| setResult(resultNode); |
| return true; |
| } |
| |
| case JSWeakSetAddIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| addToGraph(Check, Edge(key, ObjectUse)); |
| Node* hash = addToGraph(MapHash, key); |
| addToGraph(WeakSetAdd, Edge(base, WeakSetObjectUse), Edge(key, ObjectUse), Edge(hash, Int32Use)); |
| setResult(base); |
| return true; |
| } |
| |
| case JSWeakMapSetIntrinsic: { |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* value = get(virtualRegisterForArgument(2, registerOffset)); |
| |
| addToGraph(Check, Edge(key, ObjectUse)); |
| Node* hash = addToGraph(MapHash, key); |
| |
| addVarArgChild(Edge(base, WeakMapObjectUse)); |
| addVarArgChild(Edge(key, ObjectUse)); |
| addVarArgChild(Edge(value)); |
| addVarArgChild(Edge(hash, Int32Use)); |
| addToGraph(Node::VarArg, WeakMapSet, OpInfo(0), OpInfo(0)); |
| setResult(base); |
| return true; |
| } |
| |
| case DatePrototypeGetTimeIntrinsic: { |
| if (!is64Bit()) |
| return false; |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| setResult(addToGraph(DateGetTime, OpInfo(intrinsic), OpInfo(), base)); |
| return true; |
| } |
| |
| case DatePrototypeGetFullYearIntrinsic: |
| case DatePrototypeGetUTCFullYearIntrinsic: |
| case DatePrototypeGetMonthIntrinsic: |
| case DatePrototypeGetUTCMonthIntrinsic: |
| case DatePrototypeGetDateIntrinsic: |
| case DatePrototypeGetUTCDateIntrinsic: |
| case DatePrototypeGetDayIntrinsic: |
| case DatePrototypeGetUTCDayIntrinsic: |
| case DatePrototypeGetHoursIntrinsic: |
| case DatePrototypeGetUTCHoursIntrinsic: |
| case DatePrototypeGetMinutesIntrinsic: |
| case DatePrototypeGetUTCMinutesIntrinsic: |
| case DatePrototypeGetSecondsIntrinsic: |
| case DatePrototypeGetUTCSecondsIntrinsic: |
| case DatePrototypeGetMillisecondsIntrinsic: |
| case DatePrototypeGetUTCMillisecondsIntrinsic: |
| case DatePrototypeGetTimezoneOffsetIntrinsic: |
| case DatePrototypeGetYearIntrinsic: { |
| if (!is64Bit()) |
| return false; |
| insertChecks(); |
| Node* base = get(virtualRegisterForArgument(0, registerOffset)); |
| setResult(addToGraph(DateGetInt32OrNaN, OpInfo(intrinsic), OpInfo(prediction), base)); |
| return true; |
| } |
| |
| case DataViewGetInt8: |
| case DataViewGetUint8: |
| case DataViewGetInt16: |
| case DataViewGetUint16: |
| case DataViewGetInt32: |
| case DataViewGetUint32: |
| case DataViewGetFloat32: |
| case DataViewGetFloat64: { |
| if (!is64Bit()) |
| return false; |
| |
| // To inline data view accesses, we assume the architecture we're running on: |
| // - Is little endian. |
| // - Allows unaligned loads/stores without crashing. |
| |
| if (argumentCountIncludingThis < 2) |
| return false; |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| |
| uint8_t byteSize; |
| NodeType op = DataViewGetInt; |
| bool isSigned = false; |
| switch (intrinsic) { |
| case DataViewGetInt8: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewGetUint8: |
| byteSize = 1; |
| break; |
| |
| case DataViewGetInt16: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewGetUint16: |
| byteSize = 2; |
| break; |
| |
| case DataViewGetInt32: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewGetUint32: |
| byteSize = 4; |
| break; |
| |
| case DataViewGetFloat32: |
| byteSize = 4; |
| op = DataViewGetFloat; |
| break; |
| case DataViewGetFloat64: |
| byteSize = 8; |
| op = DataViewGetFloat; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| TriState isLittleEndian = MixedTriState; |
| Node* littleEndianChild = nullptr; |
| if (byteSize > 1) { |
| if (argumentCountIncludingThis < 3) |
| isLittleEndian = FalseTriState; |
| else { |
| littleEndianChild = get(virtualRegisterForArgument(2, registerOffset)); |
| if (littleEndianChild->hasConstant()) { |
| JSValue constant = littleEndianChild->constant()->value(); |
| if (constant) { |
| isLittleEndian = constant.pureToBoolean(); |
| if (isLittleEndian != MixedTriState) |
| littleEndianChild = nullptr; |
| } |
| } else |
| isLittleEndian = MixedTriState; |
| } |
| } |
| |
| DataViewData data { }; |
| data.isLittleEndian = isLittleEndian; |
| data.isSigned = isSigned; |
| data.byteSize = byteSize; |
| |
| setResult( |
| addToGraph(op, OpInfo(data.asQuadWord), OpInfo(prediction), get(virtualRegisterForArgument(0, registerOffset)), get(virtualRegisterForArgument(1, registerOffset)), littleEndianChild)); |
| return true; |
| } |
| |
| case DataViewSetInt8: |
| case DataViewSetUint8: |
| case DataViewSetInt16: |
| case DataViewSetUint16: |
| case DataViewSetInt32: |
| case DataViewSetUint32: |
| case DataViewSetFloat32: |
| case DataViewSetFloat64: { |
| if (!is64Bit()) |
| return false; |
| |
| if (argumentCountIncludingThis < 3) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| |
| uint8_t byteSize; |
| bool isFloatingPoint = false; |
| bool isSigned = false; |
| switch (intrinsic) { |
| case DataViewSetInt8: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewSetUint8: |
| byteSize = 1; |
| break; |
| |
| case DataViewSetInt16: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewSetUint16: |
| byteSize = 2; |
| break; |
| |
| case DataViewSetInt32: |
| isSigned = true; |
| FALLTHROUGH; |
| case DataViewSetUint32: |
| byteSize = 4; |
| break; |
| |
| case DataViewSetFloat32: |
| isFloatingPoint = true; |
| byteSize = 4; |
| break; |
| case DataViewSetFloat64: |
| isFloatingPoint = true; |
| byteSize = 8; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| TriState isLittleEndian = MixedTriState; |
| Node* littleEndianChild = nullptr; |
| if (byteSize > 1) { |
| if (argumentCountIncludingThis < 4) |
| isLittleEndian = FalseTriState; |
| else { |
| littleEndianChild = get(virtualRegisterForArgument(3, registerOffset)); |
| if (littleEndianChild->hasConstant()) { |
| JSValue constant = littleEndianChild->constant()->value(); |
| if (constant) { |
| isLittleEndian = constant.pureToBoolean(); |
| if (isLittleEndian != MixedTriState) |
| littleEndianChild = nullptr; |
| } |
| } else |
| isLittleEndian = MixedTriState; |
| } |
| } |
| |
| DataViewData data { }; |
| data.isLittleEndian = isLittleEndian; |
| data.isSigned = isSigned; |
| data.byteSize = byteSize; |
| data.isFloatingPoint = isFloatingPoint; |
| |
| addVarArgChild(get(virtualRegisterForArgument(0, registerOffset))); |
| addVarArgChild(get(virtualRegisterForArgument(1, registerOffset))); |
| addVarArgChild(get(virtualRegisterForArgument(2, registerOffset))); |
| addVarArgChild(littleEndianChild); |
| |
| addToGraph(Node::VarArg, DataViewSet, OpInfo(data.asQuadWord), OpInfo()); |
| setResult(addToGraph(JSConstant, OpInfo(m_constantUndefined))); |
| return true; |
| } |
| |
| case HasOwnPropertyIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| // This can be racy, that's fine. We know that once we observe that this is created, |
| // that it will never be destroyed until the VM is destroyed. It's unlikely that |
| // we'd ever get to the point where we inline this as an intrinsic without the |
| // cache being created, however, it's possible if we always throw exceptions inside |
| // hasOwnProperty. |
| if (!m_vm->hasOwnPropertyCache()) |
| return false; |
| |
| insertChecks(); |
| Node* object = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* key = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* resultNode = addToGraph(HasOwnProperty, object, key); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case StringPrototypeSliceIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* thisString = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* start = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* end = nullptr; |
| if (argumentCountIncludingThis > 2) |
| end = get(virtualRegisterForArgument(2, registerOffset)); |
| Node* resultNode = addToGraph(StringSlice, thisString, start, end); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case StringPrototypeToLowerCaseIntrinsic: { |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* thisString = get(virtualRegisterForArgument(0, registerOffset)); |
| Node* resultNode = addToGraph(ToLowerCase, thisString); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case NumberPrototypeToStringIntrinsic: { |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| insertChecks(); |
| Node* thisNumber = get(virtualRegisterForArgument(0, registerOffset)); |
| if (argumentCountIncludingThis == 1) { |
| Node* resultNode = addToGraph(ToString, thisNumber); |
| setResult(resultNode); |
| } else { |
| Node* radix = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* resultNode = addToGraph(NumberToStringWithRadix, thisNumber, radix); |
| setResult(resultNode); |
| } |
| return true; |
| } |
| |
| case NumberIsIntegerIntrinsic: { |
| if (argumentCountIncludingThis < 2) |
| return false; |
| |
| insertChecks(); |
| Node* input = get(virtualRegisterForArgument(1, registerOffset)); |
| Node* resultNode = addToGraph(NumberIsInteger, input); |
| setResult(resultNode); |
| return true; |
| } |
| |
| case CPUMfenceIntrinsic: |
| case CPURdtscIntrinsic: |
| case CPUCpuidIntrinsic: |
| case CPUPauseIntrinsic: { |
| #if CPU(X86_64) |
| if (!m_graph.m_plan.isFTL()) |
| return false; |
| insertChecks(); |
| setResult(addToGraph(CPUIntrinsic, OpInfo(intrinsic), OpInfo())); |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| default: |
| return false; |
| } |
| }; |
| |
| if (inlineIntrinsic()) { |
| RELEASE_ASSERT(didSetResult); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleDOMJITCall(Node* callTarget, VirtualRegister result, const DOMJIT::Signature* signature, int registerOffset, int argumentCountIncludingThis, SpeculatedType prediction, const ChecksFunctor& insertChecks) |
| { |
| if (argumentCountIncludingThis != static_cast<int>(1 + signature->argumentCount)) |
| return false; |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType)) |
| return false; |
| |
| // FIXME: Currently, we only support functions which arguments are up to 2. |
| // Eventually, we should extend this. But possibly, 2 or 3 can cover typical use cases. |
| // https://bugs.webkit.org/show_bug.cgi?id=164346 |
| ASSERT_WITH_MESSAGE(argumentCountIncludingThis <= JSC_DOMJIT_SIGNATURE_MAX_ARGUMENTS_INCLUDING_THIS, "Currently CallDOM does not support an arbitrary length arguments."); |
| |
| insertChecks(); |
| addCall(result, Call, signature, callTarget, argumentCountIncludingThis, registerOffset, prediction); |
| return true; |
| } |
| |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleIntrinsicGetter(VirtualRegister result, SpeculatedType prediction, const GetByIdVariant& variant, Node* thisNode, const ChecksFunctor& insertChecks) |
| { |
| switch (variant.intrinsic()) { |
| case TypedArrayByteLengthIntrinsic: { |
| insertChecks(); |
| |
| TypedArrayType type = (*variant.structureSet().begin())->classInfo()->typedArrayStorageType; |
| Array::Type arrayType = toArrayType(type); |
| size_t logSize = logElementSize(type); |
| |
| variant.structureSet().forEach([&] (Structure* structure) { |
| TypedArrayType curType = structure->classInfo()->typedArrayStorageType; |
| ASSERT(logSize == logElementSize(curType)); |
| arrayType = refineTypedArrayType(arrayType, curType); |
| ASSERT(arrayType != Array::Generic); |
| }); |
| |
| Node* lengthNode = addToGraph(GetArrayLength, OpInfo(ArrayMode(arrayType, Array::Read).asWord()), thisNode); |
| |
| if (!logSize) { |
| set(result, lengthNode); |
| return true; |
| } |
| |
| // We can use a BitLShift here because typed arrays will never have a byteLength |
| // that overflows int32. |
| Node* shiftNode = jsConstant(jsNumber(logSize)); |
| set(result, addToGraph(ArithBitLShift, lengthNode, shiftNode)); |
| |
| return true; |
| } |
| |
| case TypedArrayLengthIntrinsic: { |
| insertChecks(); |
| |
| TypedArrayType type = (*variant.structureSet().begin())->classInfo()->typedArrayStorageType; |
| Array::Type arrayType = toArrayType(type); |
| |
| variant.structureSet().forEach([&] (Structure* structure) { |
| TypedArrayType curType = structure->classInfo()->typedArrayStorageType; |
| arrayType = refineTypedArrayType(arrayType, curType); |
| ASSERT(arrayType != Array::Generic); |
| }); |
| |
| set(result, addToGraph(GetArrayLength, OpInfo(ArrayMode(arrayType, Array::Read).asWord()), thisNode)); |
| |
| return true; |
| |
| } |
| |
| case TypedArrayByteOffsetIntrinsic: { |
| insertChecks(); |
| |
| TypedArrayType type = (*variant.structureSet().begin())->classInfo()->typedArrayStorageType; |
| Array::Type arrayType = toArrayType(type); |
| |
| variant.structureSet().forEach([&] (Structure* structure) { |
| TypedArrayType curType = structure->classInfo()->typedArrayStorageType; |
| arrayType = refineTypedArrayType(arrayType, curType); |
| ASSERT(arrayType != Array::Generic); |
| }); |
| |
| set(result, addToGraph(GetTypedArrayByteOffset, OpInfo(ArrayMode(arrayType, Array::Read).asWord()), thisNode)); |
| |
| return true; |
| } |
| |
| case UnderscoreProtoIntrinsic: { |
| insertChecks(); |
| |
| bool canFold = !variant.structureSet().isEmpty(); |
| JSValue prototype; |
| variant.structureSet().forEach([&] (Structure* structure) { |
| auto getPrototypeMethod = structure->classInfo()->methodTable.getPrototype; |
| MethodTable::GetPrototypeFunctionPtr defaultGetPrototype = JSObject::getPrototype; |
| if (getPrototypeMethod != defaultGetPrototype) { |
| canFold = false; |
| return; |
| } |
| |
| if (structure->hasPolyProto()) { |
| canFold = false; |
| return; |
| } |
| if (!prototype) |
| prototype = structure->storedPrototype(); |
| else if (prototype != structure->storedPrototype()) |
| canFold = false; |
| }); |
| |
| // OK, only one prototype is found. We perform constant folding here. |
| // This information is important for super's constructor call to get new.target constant. |
| if (prototype && canFold) { |
| set(result, weakJSConstant(prototype)); |
| return true; |
| } |
| |
| set(result, addToGraph(GetPrototypeOf, OpInfo(0), OpInfo(prediction), thisNode)); |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| static void blessCallDOMGetter(Node* node) |
| { |
| DOMJIT::CallDOMGetterSnippet* snippet = node->callDOMGetterData()->snippet; |
| if (snippet && !snippet->effect.mustGenerate()) |
| node->clearFlags(NodeMustGenerate); |
| } |
| |
| bool ByteCodeParser::handleDOMJITGetter(VirtualRegister result, const GetByIdVariant& variant, Node* thisNode, unsigned identifierNumber, SpeculatedType prediction) |
| { |
| if (!variant.domAttribute()) |
| return false; |
| |
| auto* domAttribute = variant.domAttribute(); |
| |
| // We do not need to actually look up CustomGetterSetter here. Checking Structures or registering watchpoints are enough, |
| // since replacement of CustomGetterSetter always incurs Structure transition. |
| if (!check(variant.conditionSet())) |
| return false; |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.structureSet())), thisNode); |
| |
| // We do not need to emit CheckCell thingy here. When the custom accessor is replaced to different one, Structure transition occurs. |
| addToGraph(CheckSubClass, OpInfo(domAttribute->classInfo), thisNode); |
| |
| bool wasSeenInJIT = true; |
| GetByStatus* status = m_graph.m_plan.recordedStatuses().addGetByStatus(currentCodeOrigin(), GetByStatus(GetByStatus::Custom, wasSeenInJIT)); |
| bool success = status->appendVariant(variant); |
| RELEASE_ASSERT(success); |
| addToGraph(FilterGetByStatus, OpInfo(status), thisNode); |
| |
| CallDOMGetterData* callDOMGetterData = m_graph.m_callDOMGetterData.add(); |
| callDOMGetterData->customAccessorGetter = variant.customAccessorGetter(); |
| ASSERT(callDOMGetterData->customAccessorGetter); |
| |
| if (const auto* domJIT = domAttribute->domJIT) { |
| callDOMGetterData->domJIT = domJIT; |
| Ref<DOMJIT::CallDOMGetterSnippet> snippet = domJIT->compiler()(); |
| callDOMGetterData->snippet = snippet.ptr(); |
| m_graph.m_domJITSnippets.append(WTFMove(snippet)); |
| } |
| DOMJIT::CallDOMGetterSnippet* callDOMGetterSnippet = callDOMGetterData->snippet; |
| callDOMGetterData->identifierNumber = identifierNumber; |
| |
| Node* callDOMGetterNode = nullptr; |
| // GlobalObject of thisNode is always used to create a DOMWrapper. |
| if (callDOMGetterSnippet && callDOMGetterSnippet->requireGlobalObject) { |
| Node* globalObject = addToGraph(GetGlobalObject, thisNode); |
| callDOMGetterNode = addToGraph(CallDOMGetter, OpInfo(callDOMGetterData), OpInfo(prediction), thisNode, globalObject); |
| } else |
| callDOMGetterNode = addToGraph(CallDOMGetter, OpInfo(callDOMGetterData), OpInfo(prediction), thisNode); |
| blessCallDOMGetter(callDOMGetterNode); |
| set(result, callDOMGetterNode); |
| return true; |
| } |
| |
| bool ByteCodeParser::handleModuleNamespaceLoad(VirtualRegister result, SpeculatedType prediction, Node* base, GetByStatus getById) |
| { |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) |
| return false; |
| addToGraph(CheckCell, OpInfo(m_graph.freeze(getById.moduleNamespaceObject())), Edge(base, CellUse)); |
| |
| addToGraph(FilterGetByStatus, OpInfo(m_graph.m_plan.recordedStatuses().addGetByStatus(currentCodeOrigin(), getById)), base); |
| |
| // Ideally we wouldn't have to do this Phantom. But: |
| // |
| // For the constant case: we must do it because otherwise we would have no way of knowing |
| // that the scope is live at OSR here. |
| // |
| // For the non-constant case: GetClosureVar could be DCE'd, but baseline's implementation |
| // won't be able to handle an Undefined scope. |
| addToGraph(Phantom, base); |
| |
| // Constant folding in the bytecode parser is important for performance. This may not |
| // have executed yet. If it hasn't, then we won't have a prediction. Lacking a |
| // prediction, we'd otherwise think that it has to exit. Then when it did execute, we |
| // would recompile. But if we can fold it here, we avoid the exit. |
| m_graph.freeze(getById.moduleEnvironment()); |
| if (JSValue value = m_graph.tryGetConstantClosureVar(getById.moduleEnvironment(), getById.scopeOffset())) { |
| set(result, weakJSConstant(value)); |
| return true; |
| } |
| set(result, addToGraph(GetClosureVar, OpInfo(getById.scopeOffset().offset()), OpInfo(prediction), weakJSConstant(getById.moduleEnvironment()))); |
| return true; |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleTypedArrayConstructor( |
| VirtualRegister result, InternalFunction* function, int registerOffset, |
| int argumentCountIncludingThis, TypedArrayType type, const ChecksFunctor& insertChecks) |
| { |
| if (!isTypedView(type)) |
| return false; |
| |
| if (function->classInfo(*m_vm) != constructorClassInfoForType(type)) |
| return false; |
| |
| if (function->globalObject() != m_inlineStackTop->m_codeBlock->globalObject()) |
| return false; |
| |
| // We only have an intrinsic for the case where you say: |
| // |
| // new FooArray(blah); |
| // |
| // Of course, 'blah' could be any of the following: |
| // |
| // - Integer, indicating that you want to allocate an array of that length. |
| // This is the thing we're hoping for, and what we can actually do meaningful |
| // optimizations for. |
| // |
| // - Array buffer, indicating that you want to create a view onto that _entire_ |
| // buffer. |
| // |
| // - Non-buffer object, indicating that you want to create a copy of that |
| // object by pretending that it quacks like an array. |
| // |
| // - Anything else, indicating that you want to have an exception thrown at |
| // you. |
| // |
| // The intrinsic, NewTypedArray, will behave as if it could do any of these |
| // things up until we do Fixup. Thereafter, if child1 (i.e. 'blah') is |
| // predicted Int32, then we lock it in as a normal typed array allocation. |
| // Otherwise, NewTypedArray turns into a totally opaque function call that |
| // may clobber the world - by virtue of it accessing properties on what could |
| // be an object. |
| // |
| // Note that although the generic form of NewTypedArray sounds sort of awful, |
| // it is actually quite likely to be more efficient than a fully generic |
| // Construct. So, we might want to think about making NewTypedArray variadic, |
| // or else making Construct not super slow. |
| |
| if (argumentCountIncludingThis != 2) |
| return false; |
| |
| if (!function->globalObject()->typedArrayStructureConcurrently(type)) |
| return false; |
| |
| insertChecks(); |
| set(result, |
| addToGraph(NewTypedArray, OpInfo(type), get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| template<typename ChecksFunctor> |
| bool ByteCodeParser::handleConstantInternalFunction( |
| Node* callTargetNode, VirtualRegister result, InternalFunction* function, int registerOffset, |
| int argumentCountIncludingThis, CodeSpecializationKind kind, SpeculatedType prediction, const ChecksFunctor& insertChecks) |
| { |
| VERBOSE_LOG(" Handling constant internal function ", JSValue(function), "\n"); |
| |
| // It so happens that the code below assumes that the result operand is valid. It's extremely |
| // unlikely that the result operand would be invalid - you'd have to call this via a setter call. |
| if (!result.isValid()) |
| return false; |
| |
| if (kind == CodeForConstruct) { |
| Node* newTargetNode = get(virtualRegisterForArgument(0, registerOffset)); |
| // We cannot handle the case where new.target != callee (i.e. a construct from a super call) because we |
| // don't know what the prototype of the constructed object will be. |
| // FIXME: If we have inlined super calls up to the call site, however, we should be able to figure out the structure. https://bugs.webkit.org/show_bug.cgi?id=152700 |
| if (newTargetNode != callTargetNode) |
| return false; |
| } |
| |
| if (function->classInfo(*m_vm) == ArrayConstructor::info()) { |
| if (function->globalObject() != m_inlineStackTop->m_codeBlock->globalObject()) |
| return false; |
| |
| insertChecks(); |
| if (argumentCountIncludingThis == 2) { |
| set(result, |
| addToGraph(NewArrayWithSize, OpInfo(ArrayWithUndecided), get(virtualRegisterForArgument(1, registerOffset)))); |
| return true; |
| } |
| |
| for (int i = 1; i < argumentCountIncludingThis; ++i) |
| addVarArgChild(get(virtualRegisterForArgument(i, registerOffset))); |
| set(result, |
| addToGraph(Node::VarArg, NewArray, OpInfo(ArrayWithUndecided), OpInfo(argumentCountIncludingThis - 1))); |
| return true; |
| } |
| |
| if (function->classInfo(*m_vm) == NumberConstructor::info()) { |
| if (kind == CodeForConstruct) |
| return false; |
| |
| insertChecks(); |
| if (argumentCountIncludingThis <= 1) |
| set(result, jsConstant(jsNumber(0))); |
| else |
| set(result, addToGraph(ToNumber, OpInfo(0), OpInfo(prediction), get(virtualRegisterForArgument(1, registerOffset)))); |
| |
| return true; |
| } |
| |
| if (function->classInfo(*m_vm) == StringConstructor::info()) { |
| insertChecks(); |
| |
| Node* resultNode; |
| |
| if (argumentCountIncludingThis <= 1) |
| resultNode = jsConstant(m_vm->smallStrings.emptyString()); |
| else |
| resultNode = addToGraph(CallStringConstructor, get(virtualRegisterForArgument(1, registerOffset))); |
| |
| if (kind == CodeForConstruct) |
| resultNode = addToGraph(NewStringObject, OpInfo(m_graph.registerStructure(function->globalObject()->stringObjectStructure())), resultNode); |
| |
| set(result, resultNode); |
| return true; |
| } |
| |
| if (function->classInfo(*m_vm) == SymbolConstructor::info() && kind == CodeForCall) { |
| insertChecks(); |
| |
| Node* resultNode; |
| |
| if (argumentCountIncludingThis <= 1) |
| resultNode = addToGraph(NewSymbol); |
| else |
| resultNode = addToGraph(NewSymbol, addToGraph(ToString, get(virtualRegisterForArgument(1, registerOffset)))); |
| |
| set(result, resultNode); |
| return true; |
| } |
| |
| // FIXME: This should handle construction as well. https://bugs.webkit.org/show_bug.cgi?id=155591 |
| if (function->classInfo(*m_vm) == ObjectConstructor::info() && kind == CodeForCall) { |
| insertChecks(); |
| |
| Node* resultNode; |
| if (argumentCountIncludingThis <= 1) |
| resultNode = addToGraph(NewObject, OpInfo(m_graph.registerStructure(function->globalObject()->objectStructureForObjectConstructor()))); |
| else |
| resultNode = addToGraph(CallObjectConstructor, OpInfo(m_graph.freeze(function->globalObject())), OpInfo(prediction), get(virtualRegisterForArgument(1, registerOffset))); |
| set(result, resultNode); |
| return true; |
| } |
| |
| for (unsigned typeIndex = 0; typeIndex < NumberOfTypedArrayTypes; ++typeIndex) { |
| bool handled = handleTypedArrayConstructor( |
| result, function, registerOffset, argumentCountIncludingThis, |
| indexToTypedArrayType(typeIndex), insertChecks); |
| if (handled) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| Node* ByteCodeParser::handleGetByOffset( |
| SpeculatedType prediction, Node* base, unsigned identifierNumber, PropertyOffset offset, NodeType op) |
| { |
| Node* propertyStorage; |
| if (isInlineOffset(offset)) |
| propertyStorage = base; |
| else |
| propertyStorage = addToGraph(GetButterfly, base); |
| |
| StorageAccessData* data = m_graph.m_storageAccessData.add(); |
| data->offset = offset; |
| data->identifierNumber = identifierNumber; |
| |
| Node* getByOffset = addToGraph(op, OpInfo(data), OpInfo(prediction), propertyStorage, base); |
| |
| return getByOffset; |
| } |
| |
| Node* ByteCodeParser::handlePutByOffset( |
| Node* base, unsigned identifier, PropertyOffset offset, |
| Node* value) |
| { |
| Node* propertyStorage; |
| if (isInlineOffset(offset)) |
| propertyStorage = base; |
| else |
| propertyStorage = addToGraph(GetButterfly, base); |
| |
| StorageAccessData* data = m_graph.m_storageAccessData.add(); |
| data->offset = offset; |
| data->identifierNumber = identifier; |
| |
| Node* result = addToGraph(PutByOffset, OpInfo(data), propertyStorage, base, value); |
| |
| return result; |
| } |
| |
| bool ByteCodeParser::check(const ObjectPropertyCondition& condition) |
| { |
| if (!condition) |
| return false; |
| |
| if (m_graph.watchCondition(condition)) |
| return true; |
| |
| Structure* structure = condition.object()->structure(*m_vm); |
| if (!condition.structureEnsuresValidity(structure)) |
| return false; |
| |
| addToGraph( |
| CheckStructure, |
| OpInfo(m_graph.addStructureSet(structure)), |
| weakJSConstant(condition.object())); |
| return true; |
| } |
| |
| GetByOffsetMethod ByteCodeParser::promoteToConstant(GetByOffsetMethod method) |
| { |
| if (method.kind() == GetByOffsetMethod::LoadFromPrototype |
| && method.prototype()->structure()->dfgShouldWatch()) { |
| if (JSValue constant = m_graph.tryGetConstantProperty(method.prototype()->value(), method.prototype()->structure(), method.offset())) |
| return GetByOffsetMethod::constant(m_graph.freeze(constant)); |
| } |
| |
| return method; |
| } |
| |
| bool ByteCodeParser::needsDynamicLookup(ResolveType type, OpcodeID opcode) |
| { |
| ASSERT(opcode == op_resolve_scope || opcode == op_get_from_scope || opcode == op_put_to_scope); |
| |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| if (needsVarInjectionChecks(type) && globalObject->varInjectionWatchpoint()->hasBeenInvalidated()) |
| return true; |
| |
| switch (type) { |
| case GlobalProperty: |
| case GlobalVar: |
| case GlobalLexicalVar: |
| case ClosureVar: |
| case LocalClosureVar: |
| case ModuleVar: |
| return false; |
| |
| case UnresolvedProperty: |
| case UnresolvedPropertyWithVarInjectionChecks: { |
| // The heuristic for UnresolvedProperty scope accesses is we will ForceOSRExit if we |
| // haven't exited from from this access before to let the baseline JIT try to better |
| // cache the access. If we've already exited from this operation, it's unlikely that |
| // the baseline will come up with a better ResolveType and instead we will compile |
| // this as a dynamic scope access. |
| |
| // We only track our heuristic through resolve_scope since resolve_scope will |
| // dominate unresolved gets/puts on that scope. |
| if (opcode != op_resolve_scope) |
| return true; |
| |
| if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, InadequateCoverage)) { |
| // We've already exited so give up on getting better ResolveType information. |
| return true; |
| } |
| |
| // We have not exited yet, so let's have the baseline get better ResolveType information for us. |
| // This type of code is often seen when we tier up in a loop but haven't executed the part |
| // of a function that comes after the loop. |
| return false; |
| } |
| |
| case Dynamic: |
| return true; |
| |
| case GlobalPropertyWithVarInjectionChecks: |
| case GlobalVarWithVarInjectionChecks: |
| case GlobalLexicalVarWithVarInjectionChecks: |
| case ClosureVarWithVarInjectionChecks: |
| return false; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| GetByOffsetMethod ByteCodeParser::planLoad(const ObjectPropertyCondition& condition) |
| { |
| VERBOSE_LOG("Planning a load: ", condition, "\n"); |
| |
| // We might promote this to Equivalence, and a later DFG pass might also do such promotion |
| // even if we fail, but for simplicity this cannot be asked to load an equivalence condition. |
| // None of the clients of this method will request a load of an Equivalence condition anyway, |
| // and supporting it would complicate the heuristics below. |
| RELEASE_ASSERT(condition.kind() == PropertyCondition::Presence); |
| |
| // Here's the ranking of how to handle this, from most preferred to least preferred: |
| // |
| // 1) Watchpoint on an equivalence condition and return a constant node for the loaded value. |
| // No other code is emitted, and the structure of the base object is never registered. |
| // Hence this results in zero code and we won't jettison this compilation if the object |
| // transitions, even if the structure is watchable right now. |
| // |
| // 2) Need to emit a load, and the current structure of the base is going to be watched by the |
| // DFG anyway (i.e. dfgShouldWatch). Watch the structure and emit the load. Don't watch the |
| // condition, since the act of turning the base into a constant in IR will cause the DFG to |
| // watch the structure anyway and doing so would subsume watching the condition. |
| // |
| // 3) Need to emit a load, and the current structure of the base is watchable but not by the |
| // DFG (i.e. transitionWatchpointSetIsStillValid() and !dfgShouldWatchIfPossible()). Watch |
| // the condition, and emit a load. |
| // |
| // 4) Need to emit a load, and the current structure of the base is not watchable. Emit a |
| // structure check, and emit a load. |
| // |
| // 5) The condition does not hold. Give up and return null. |
| |
| // First, try to promote Presence to Equivalence. We do this before doing anything else |
| // because it's the most profitable. Also, there are cases where the presence is watchable but |
| // we don't want to watch it unless it became an equivalence (see the relationship between |
| // (1), (2), and (3) above). |
| ObjectPropertyCondition equivalenceCondition = condition.attemptToMakeEquivalenceWithoutBarrier(*m_vm); |
| if (m_graph.watchCondition(equivalenceCondition)) |
| return GetByOffsetMethod::constant(m_graph.freeze(equivalenceCondition.requiredValue())); |
| |
| // At this point, we'll have to materialize the condition's base as a constant in DFG IR. Once |
| // we do this, the frozen value will have its own idea of what the structure is. Use that from |
| // now on just because it's less confusing. |
| FrozenValue* base = m_graph.freeze(condition.object()); |
| Structure* structure = base->structure(); |
| |
| // Check if the structure that we've registered makes the condition hold. If not, just give |
| // up. This is case (5) above. |
| if (!condition.structureEnsuresValidity(structure)) |
| return GetByOffsetMethod(); |
| |
| // If the structure is watched by the DFG already, then just use this fact to emit the load. |
| // This is case (2) above. |
| if (structure->dfgShouldWatch()) |
| return promoteToConstant(GetByOffsetMethod::loadFromPrototype(base, condition.offset())); |
| |
| // If we can watch the condition right now, then we can emit the load after watching it. This |
| // is case (3) above. |
| if (m_graph.watchCondition(condition)) |
| return promoteToConstant(GetByOffsetMethod::loadFromPrototype(base, condition.offset())); |
| |
| // We can't watch anything but we know that the current structure satisfies the condition. So, |
| // check for that structure and then emit the load. |
| addToGraph( |
| CheckStructure, |
| OpInfo(m_graph.addStructureSet(structure)), |
| addToGraph(JSConstant, OpInfo(base))); |
| return promoteToConstant(GetByOffsetMethod::loadFromPrototype(base, condition.offset())); |
| } |
| |
| Node* ByteCodeParser::load( |
| SpeculatedType prediction, unsigned identifierNumber, const GetByOffsetMethod& method, |
| NodeType op) |
| { |
| switch (method.kind()) { |
| case GetByOffsetMethod::Invalid: |
| return nullptr; |
| case GetByOffsetMethod::Constant: |
| return addToGraph(JSConstant, OpInfo(method.constant())); |
| case GetByOffsetMethod::LoadFromPrototype: { |
| Node* baseNode = addToGraph(JSConstant, OpInfo(method.prototype())); |
| return handleGetByOffset( |
| prediction, baseNode, identifierNumber, method.offset(), op); |
| } |
| case GetByOffsetMethod::Load: |
| // Will never see this from planLoad(). |
| RELEASE_ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| Node* ByteCodeParser::load( |
| SpeculatedType prediction, const ObjectPropertyCondition& condition, NodeType op) |
| { |
| GetByOffsetMethod method = planLoad(condition); |
| return load(prediction, m_graph.identifiers().ensure(condition.uid()), method, op); |
| } |
| |
| bool ByteCodeParser::check(const ObjectPropertyConditionSet& conditionSet) |
| { |
| for (const ObjectPropertyCondition& condition : conditionSet) { |
| if (!check(condition)) |
| return false; |
| } |
| return true; |
| } |
| |
| GetByOffsetMethod ByteCodeParser::planLoad(const ObjectPropertyConditionSet& conditionSet) |
| { |
| VERBOSE_LOG("conditionSet = ", conditionSet, "\n"); |
| |
| GetByOffsetMethod result; |
| for (const ObjectPropertyCondition& condition : conditionSet) { |
| switch (condition.kind()) { |
| case PropertyCondition::Presence: |
| RELEASE_ASSERT(!result); // Should only see exactly one of these. |
| result = planLoad(condition); |
| if (!result) |
| return GetByOffsetMethod(); |
| break; |
| default: |
| if (!check(condition)) |
| return GetByOffsetMethod(); |
| break; |
| } |
| } |
| if (!result) { |
| // We have a unset property. |
| ASSERT(!conditionSet.numberOfConditionsWithKind(PropertyCondition::Presence)); |
| return GetByOffsetMethod::constant(m_constantUndefined); |
| } |
| return result; |
| } |
| |
| Node* ByteCodeParser::load( |
| SpeculatedType prediction, const ObjectPropertyConditionSet& conditionSet, NodeType op) |
| { |
| GetByOffsetMethod method = planLoad(conditionSet); |
| return load( |
| prediction, |
| m_graph.identifiers().ensure(conditionSet.slotBaseCondition().uid()), |
| method, op); |
| } |
| |
| ObjectPropertyCondition ByteCodeParser::presenceLike( |
| JSObject* knownBase, UniquedStringImpl* uid, PropertyOffset offset, const StructureSet& set) |
| { |
| if (set.isEmpty()) |
| return ObjectPropertyCondition(); |
| unsigned attributes; |
| PropertyOffset firstOffset = set[0]->getConcurrently(uid, attributes); |
| if (firstOffset != offset) |
| return ObjectPropertyCondition(); |
| for (unsigned i = 1; i < set.size(); ++i) { |
| unsigned otherAttributes; |
| PropertyOffset otherOffset = set[i]->getConcurrently(uid, otherAttributes); |
| if (otherOffset != offset || otherAttributes != attributes) |
| return ObjectPropertyCondition(); |
| } |
| return ObjectPropertyCondition::presenceWithoutBarrier(knownBase, uid, offset, attributes); |
| } |
| |
| bool ByteCodeParser::checkPresenceLike( |
| JSObject* knownBase, UniquedStringImpl* uid, PropertyOffset offset, const StructureSet& set) |
| { |
| return check(presenceLike(knownBase, uid, offset, set)); |
| } |
| |
| void ByteCodeParser::checkPresenceLike( |
| Node* base, UniquedStringImpl* uid, PropertyOffset offset, const StructureSet& set) |
| { |
| if (JSObject* knownBase = base->dynamicCastConstant<JSObject*>(*m_vm)) { |
| if (checkPresenceLike(knownBase, uid, offset, set)) |
| return; |
| } |
| |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(set)), base); |
| } |
| |
| template<typename VariantType> |
| Node* ByteCodeParser::load( |
| SpeculatedType prediction, Node* base, unsigned identifierNumber, const VariantType& variant) |
| { |
| // Make sure backwards propagation knows that we've used base. |
| addToGraph(Phantom, base); |
| |
| bool needStructureCheck = true; |
| |
| UniquedStringImpl* uid = m_graph.identifiers()[identifierNumber]; |
| |
| if (JSObject* knownBase = base->dynamicCastConstant<JSObject*>(*m_vm)) { |
| // Try to optimize away the structure check. Note that it's not worth doing anything about this |
| // if the base's structure is watched. |
| Structure* structure = base->constant()->structure(); |
| if (!structure->dfgShouldWatch()) { |
| if (!variant.conditionSet().isEmpty()) { |
| // This means that we're loading from a prototype or we have a property miss. We expect |
| // the base not to have the property. We can only use ObjectPropertyCondition if all of |
| // the structures in the variant.structureSet() agree on the prototype (it would be |
| // hilariously rare if they didn't). Note that we are relying on structureSet() having |
| // at least one element. That will always be true here because of how GetByStatus/PutByIdStatus work. |
| |
| // FIXME: right now, if we have an OPCS, we have mono proto. However, this will |
| // need to be changed in the future once we have a hybrid data structure for |
| // poly proto: |
| // https://bugs.webkit.org/show_bug.cgi?id=177339 |
| JSObject* prototype = variant.structureSet()[0]->storedPrototypeObject(); |
| bool allAgree = true; |
| for (unsigned i = 1; i < variant.structureSet().size(); ++i) { |
| if (variant.structureSet()[i]->storedPrototypeObject() != prototype) { |
| allAgree = false; |
| break; |
| } |
| } |
| if (allAgree) { |
| ObjectPropertyCondition condition = ObjectPropertyCondition::absenceWithoutBarrier( |
| knownBase, uid, prototype); |
| if (check(condition)) |
| needStructureCheck = false; |
| } |
| } else { |
| // This means we're loading directly from base. We can avoid all of the code that follows |
| // if we can prove that the property is a constant. Otherwise, we try to prove that the |
| // property is watchably present, in which case we get rid of the structure check. |
| |
| ObjectPropertyCondition presenceCondition = |
| presenceLike(knownBase, uid, variant.offset(), variant.structureSet()); |
| if (presenceCondition) { |
| ObjectPropertyCondition equivalenceCondition = |
| presenceCondition.attemptToMakeEquivalenceWithoutBarrier(*m_vm); |
| if (m_graph.watchCondition(equivalenceCondition)) |
| return weakJSConstant(equivalenceCondition.requiredValue()); |
| |
| if (check(presenceCondition)) |
| needStructureCheck = false; |
| } |
| } |
| } |
| } |
| |
| if (needStructureCheck) |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.structureSet())), base); |
| |
| if (variant.isPropertyUnset()) { |
| if (m_graph.watchConditions(variant.conditionSet())) |
| return jsConstant(jsUndefined()); |
| return nullptr; |
| } |
| |
| SpeculatedType loadPrediction; |
| NodeType loadOp; |
| if (variant.callLinkStatus() || variant.intrinsic() != NoIntrinsic) { |
| loadPrediction = SpecCellOther; |
| loadOp = GetGetterSetterByOffset; |
| } else { |
| loadPrediction = prediction; |
| loadOp = GetByOffset; |
| } |
| |
| Node* loadedValue; |
| if (!variant.conditionSet().isEmpty()) |
| loadedValue = load(loadPrediction, variant.conditionSet(), loadOp); |
| else { |
| if (needStructureCheck && base->hasConstant()) { |
| // We did emit a structure check. That means that we have an opportunity to do constant folding |
| // here, since we didn't do it above. |
| JSValue constant = m_graph.tryGetConstantProperty( |
| base->asJSValue(), *m_graph.addStructureSet(variant.structureSet()), variant.offset()); |
| if (constant) |
| return weakJSConstant(constant); |
| } |
| |
| loadedValue = handleGetByOffset( |
| loadPrediction, base, identifierNumber, variant.offset(), loadOp); |
| } |
| |
| return loadedValue; |
| } |
| |
| Node* ByteCodeParser::store(Node* base, unsigned identifier, const PutByIdVariant& variant, Node* value) |
| { |
| RELEASE_ASSERT(variant.kind() == PutByIdVariant::Replace); |
| |
| checkPresenceLike(base, m_graph.identifiers()[identifier], variant.offset(), variant.structure()); |
| return handlePutByOffset(base, identifier, variant.offset(), value); |
| } |
| |
| void ByteCodeParser::handleGetById( |
| VirtualRegister destination, SpeculatedType prediction, Node* base, unsigned identifierNumber, |
| GetByStatus getByStatus, AccessType type, unsigned instructionSize) |
| { |
| // Attempt to reduce the set of things in the GetByStatus. |
| if (base->op() == NewObject) { |
| bool ok = true; |
| for (unsigned i = m_currentBlock->size(); i--;) { |
| Node* node = m_currentBlock->at(i); |
| if (node == base) |
| break; |
| if (writesOverlap(m_graph, node, JSCell_structureID)) { |
| ok = false; |
| break; |
| } |
| } |
| if (ok) |
| getByStatus.filter(base->structure().get()); |
| } |
| |
| NodeType getById; |
| if (type == AccessType::GetById) |
| getById = getByStatus.makesCalls() ? GetByIdFlush : GetById; |
| else if (type == AccessType::TryGetById) |
| getById = TryGetById; |
| else |
| getById = getByStatus.makesCalls() ? GetByIdDirectFlush : GetByIdDirect; |
| |
| if (getById != TryGetById && getByStatus.isModuleNamespace()) { |
| if (handleModuleNamespaceLoad(destination, prediction, base, getByStatus)) { |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedGetById(); |
| return; |
| } |
| } |
| |
| // Special path for custom accessors since custom's offset does not have any meanings. |
| // So, this is completely different from Simple one. But we have a chance to optimize it when we use DOMJIT. |
| if (Options::useDOMJIT() && getByStatus.isCustom()) { |
| ASSERT(getByStatus.numVariants() == 1); |
| ASSERT(!getByStatus.makesCalls()); |
| GetByIdVariant variant = getByStatus[0]; |
| ASSERT(variant.domAttribute()); |
| if (handleDOMJITGetter(destination, variant, base, identifierNumber, prediction)) { |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedGetById(); |
| return; |
| } |
| } |
| |
| ASSERT(type == AccessType::GetById || type == AccessType::GetByIdDirect || !getByStatus.makesCalls()); |
| if (!getByStatus.isSimple() || !getByStatus.numVariants() || !Options::useAccessInlining()) { |
| set(destination, |
| addToGraph(getById, OpInfo(identifierNumber), OpInfo(prediction), base)); |
| return; |
| } |
| |
| // FIXME: If we use the GetByStatus for anything then we should record it and insert a node |
| // after everything else (like the GetByOffset or whatever) that will filter the recorded |
| // GetByStatus. That means that the constant folder also needs to do the same! |
| |
| if (getByStatus.numVariants() > 1) { |
| if (getByStatus.makesCalls() || !m_graph.m_plan.isFTL() |
| || !Options::usePolymorphicAccessInlining() |
| || getByStatus.numVariants() > Options::maxPolymorphicAccessInliningListSize()) { |
| set(destination, |
| addToGraph(getById, OpInfo(identifierNumber), OpInfo(prediction), base)); |
| return; |
| } |
| |
| addToGraph(FilterGetByStatus, OpInfo(m_graph.m_plan.recordedStatuses().addGetByStatus(currentCodeOrigin(), getByStatus)), base); |
| |
| Vector<MultiGetByOffsetCase, 2> cases; |
| |
| // 1) Emit prototype structure checks for all chains. This could sort of maybe not be |
| // optimal, if there is some rarely executed case in the chain that requires a lot |
| // of checks and those checks are not watchpointable. |
| for (const GetByIdVariant& variant : getByStatus.variants()) { |
| if (variant.intrinsic() != NoIntrinsic) { |
| set(destination, |
| addToGraph(getById, OpInfo(identifierNumber), OpInfo(prediction), base)); |
| return; |
| } |
| |
| if (variant.conditionSet().isEmpty()) { |
| cases.append( |
| MultiGetByOffsetCase( |
| *m_graph.addStructureSet(variant.structureSet()), |
| GetByOffsetMethod::load(variant.offset()))); |
| continue; |
| } |
| |
| GetByOffsetMethod method = planLoad(variant.conditionSet()); |
| if (!method) { |
| set(destination, |
| addToGraph(getById, OpInfo(identifierNumber), OpInfo(prediction), base)); |
| return; |
| } |
| |
| cases.append(MultiGetByOffsetCase(*m_graph.addStructureSet(variant.structureSet()), method)); |
| } |
| |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedGetById(); |
| |
| // 2) Emit a MultiGetByOffset |
| MultiGetByOffsetData* data = m_graph.m_multiGetByOffsetData.add(); |
| data->cases = cases; |
| data->identifierNumber = identifierNumber; |
| set(destination, |
| addToGraph(MultiGetByOffset, OpInfo(data), OpInfo(prediction), base)); |
| return; |
| } |
| |
| addToGraph(FilterGetByStatus, OpInfo(m_graph.m_plan.recordedStatuses().addGetByStatus(currentCodeOrigin(), getByStatus)), base); |
| |
| ASSERT(getByStatus.numVariants() == 1); |
| GetByIdVariant variant = getByStatus[0]; |
| |
| Node* loadedValue = load(prediction, base, identifierNumber, variant); |
| if (!loadedValue) { |
| set(destination, |
| addToGraph(getById, OpInfo(identifierNumber), OpInfo(prediction), base)); |
| return; |
| } |
| |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedGetById(); |
| |
| ASSERT(type == AccessType::GetById || type == AccessType::GetByIdDirect || !variant.callLinkStatus()); |
| if (!variant.callLinkStatus() && variant.intrinsic() == NoIntrinsic) { |
| set(destination, loadedValue); |
| return; |
| } |
| |
| Node* getter = addToGraph(GetGetter, loadedValue); |
| |
| if (handleIntrinsicGetter(destination, prediction, variant, base, |
| [&] () { |
| addToGraph(CheckCell, OpInfo(m_graph.freeze(variant.intrinsicFunction())), getter); |
| })) { |
| addToGraph(Phantom, base); |
| return; |
| } |
| |
| ASSERT(variant.intrinsic() == NoIntrinsic); |
| |
| // Make a call. We don't try to get fancy with using the smallest operand number because |
| // the stack layout phase should compress the stack anyway. |
| |
| unsigned numberOfParameters = 0; |
| numberOfParameters++; // The 'this' argument. |
| numberOfParameters++; // True return PC. |
| |
| // Start with a register offset that corresponds to the last in-use register. |
| int registerOffset = virtualRegisterForLocal( |
| m_inlineStackTop->m_profiledBlock->numCalleeLocals() - 1).offset(); |
| registerOffset -= numberOfParameters; |
| registerOffset -= CallFrame::headerSizeInRegisters; |
| |
| // Get the alignment right. |
| registerOffset = -WTF::roundUpToMultipleOf( |
| stackAlignmentRegisters(), |
| -registerOffset); |
| |
| ensureLocals( |
| m_inlineStackTop->remapOperand( |
| VirtualRegister(registerOffset)).toLocal()); |
| |
| // Issue SetLocals. This has two effects: |
| // 1) That's how handleCall() sees the arguments. |
| // 2) If we inline then this ensures that the arguments are flushed so that if you use |
| // the dreaded arguments object on the getter, the right things happen. Well, sort of - |
| // since we only really care about 'this' in this case. But we're not going to take that |
| // shortcut. |
| set(virtualRegisterForArgument(0, registerOffset), base, ImmediateNakedSet); |
| |
| // We've set some locals, but they are not user-visible. It's still OK to exit from here. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| handleCall( |
| destination, Call, InlineCallFrame::GetterCall, instructionSize, |
| getter, numberOfParameters - 1, registerOffset, *variant.callLinkStatus(), prediction); |
| } |
| |
| void ByteCodeParser::emitPutById( |
| Node* base, unsigned identifierNumber, Node* value, const PutByIdStatus& putByIdStatus, bool isDirect) |
| { |
| if (isDirect) |
| addToGraph(PutByIdDirect, OpInfo(identifierNumber), base, value); |
| else |
| addToGraph(putByIdStatus.makesCalls() ? PutByIdFlush : PutById, OpInfo(identifierNumber), base, value); |
| } |
| |
| void ByteCodeParser::handlePutById( |
| Node* base, unsigned identifierNumber, Node* value, |
| const PutByIdStatus& putByIdStatus, bool isDirect, unsigned instructionSize) |
| { |
| if (!putByIdStatus.isSimple() || !putByIdStatus.numVariants() || !Options::useAccessInlining()) { |
| if (!putByIdStatus.isSet()) |
| addToGraph(ForceOSRExit); |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } |
| |
| if (putByIdStatus.numVariants() > 1) { |
| if (!m_graph.m_plan.isFTL() || putByIdStatus.makesCalls() |
| || !Options::usePolymorphicAccessInlining() |
| || putByIdStatus.numVariants() > Options::maxPolymorphicAccessInliningListSize()) { |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } |
| |
| if (!isDirect) { |
| for (unsigned variantIndex = putByIdStatus.numVariants(); variantIndex--;) { |
| if (putByIdStatus[variantIndex].kind() != PutByIdVariant::Transition) |
| continue; |
| if (!check(putByIdStatus[variantIndex].conditionSet())) { |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } |
| } |
| } |
| |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedPutById(); |
| |
| addToGraph(FilterPutByIdStatus, OpInfo(m_graph.m_plan.recordedStatuses().addPutByIdStatus(currentCodeOrigin(), putByIdStatus)), base); |
| |
| for (const PutByIdVariant& variant : putByIdStatus.variants()) { |
| for (Structure* structure : variant.oldStructure()) |
| m_graph.registerStructure(structure); |
| if (variant.kind() == PutByIdVariant::Transition) |
| m_graph.registerStructure(variant.newStructure()); |
| } |
| |
| MultiPutByOffsetData* data = m_graph.m_multiPutByOffsetData.add(); |
| data->variants = putByIdStatus.variants(); |
| data->identifierNumber = identifierNumber; |
| addToGraph(MultiPutByOffset, OpInfo(data), base, value); |
| return; |
| } |
| |
| ASSERT(putByIdStatus.numVariants() == 1); |
| const PutByIdVariant& variant = putByIdStatus[0]; |
| |
| switch (variant.kind()) { |
| case PutByIdVariant::Replace: { |
| addToGraph(FilterPutByIdStatus, OpInfo(m_graph.m_plan.recordedStatuses().addPutByIdStatus(currentCodeOrigin(), putByIdStatus)), base); |
| |
| store(base, identifierNumber, variant, value); |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedPutById(); |
| return; |
| } |
| |
| case PutByIdVariant::Transition: { |
| addToGraph(FilterPutByIdStatus, OpInfo(m_graph.m_plan.recordedStatuses().addPutByIdStatus(currentCodeOrigin(), putByIdStatus)), base); |
| |
| addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(variant.oldStructure())), base); |
| if (!check(variant.conditionSet())) { |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } |
| |
| ASSERT(variant.oldStructureForTransition()->transitionWatchpointSetHasBeenInvalidated()); |
| |
| Node* propertyStorage; |
| Transition* transition = m_graph.m_transitions.add( |
| m_graph.registerStructure(variant.oldStructureForTransition()), m_graph.registerStructure(variant.newStructure())); |
| |
| if (variant.reallocatesStorage()) { |
| |
| // If we're growing the property storage then it must be because we're |
| // storing into the out-of-line storage. |
| ASSERT(!isInlineOffset(variant.offset())); |
| |
| if (!variant.oldStructureForTransition()->outOfLineCapacity()) { |
| propertyStorage = addToGraph( |
| AllocatePropertyStorage, OpInfo(transition), base); |
| } else { |
| propertyStorage = addToGraph( |
| ReallocatePropertyStorage, OpInfo(transition), |
| base, addToGraph(GetButterfly, base)); |
| } |
| } else { |
| if (isInlineOffset(variant.offset())) |
| propertyStorage = base; |
| else |
| propertyStorage = addToGraph(GetButterfly, base); |
| } |
| |
| StorageAccessData* data = m_graph.m_storageAccessData.add(); |
| data->offset = variant.offset(); |
| data->identifierNumber = identifierNumber; |
| |
| // NOTE: We could GC at this point because someone could insert an operation that GCs. |
| // That's fine because: |
| // - Things already in the structure will get scanned because we haven't messed with |
| // the object yet. |
| // - The value we are fixing to put is going to be kept live by OSR exit handling. So |
| // if the GC does a conservative scan here it will see the new value. |
| |
| addToGraph( |
| PutByOffset, |
| OpInfo(data), |
| propertyStorage, |
| base, |
| value); |
| |
| if (variant.reallocatesStorage()) |
| addToGraph(NukeStructureAndSetButterfly, base, propertyStorage); |
| |
| // FIXME: PutStructure goes last until we fix either |
| // https://bugs.webkit.org/show_bug.cgi?id=142921 or |
| // https://bugs.webkit.org/show_bug.cgi?id=142924. |
| addToGraph(PutStructure, OpInfo(transition), base); |
| |
| if (UNLIKELY(m_graph.compilation())) |
| m_graph.compilation()->noticeInlinedPutById(); |
| return; |
| } |
| |
| case PutByIdVariant::Setter: { |
| addToGraph(FilterPutByIdStatus, OpInfo(m_graph.m_plan.recordedStatuses().addPutByIdStatus(currentCodeOrigin(), putByIdStatus)), base); |
| |
| Node* loadedValue = load(SpecCellOther, base, identifierNumber, variant); |
| if (!loadedValue) { |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } |
| |
| Node* setter = addToGraph(GetSetter, loadedValue); |
| |
| // Make a call. We don't try to get fancy with using the smallest operand number because |
| // the stack layout phase should compress the stack anyway. |
| |
| unsigned numberOfParameters = 0; |
| numberOfParameters++; // The 'this' argument. |
| numberOfParameters++; // The new value. |
| numberOfParameters++; // True return PC. |
| |
| // Start with a register offset that corresponds to the last in-use register. |
| int registerOffset = virtualRegisterForLocal( |
| m_inlineStackTop->m_profiledBlock->numCalleeLocals() - 1).offset(); |
| registerOffset -= numberOfParameters; |
| registerOffset -= CallFrame::headerSizeInRegisters; |
| |
| // Get the alignment right. |
| registerOffset = -WTF::roundUpToMultipleOf( |
| stackAlignmentRegisters(), |
| -registerOffset); |
| |
| ensureLocals( |
| m_inlineStackTop->remapOperand( |
| VirtualRegister(registerOffset)).toLocal()); |
| |
| set(virtualRegisterForArgument(0, registerOffset), base, ImmediateNakedSet); |
| set(virtualRegisterForArgument(1, registerOffset), value, ImmediateNakedSet); |
| |
| // We've set some locals, but they are not user-visible. It's still OK to exit from here. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| handleCall( |
| VirtualRegister(), Call, InlineCallFrame::SetterCall, |
| instructionSize, setter, numberOfParameters - 1, registerOffset, |
| *variant.callLinkStatus(), SpecOther); |
| return; |
| } |
| |
| default: { |
| emitPutById(base, identifierNumber, value, putByIdStatus, isDirect); |
| return; |
| } } |
| } |
| |
| void ByteCodeParser::prepareToParseBlock() |
| { |
| clearCaches(); |
| ASSERT(m_setLocalQueue.isEmpty()); |
| } |
| |
| void ByteCodeParser::clearCaches() |
| { |
| m_constants.shrink(0); |
| } |
| |
| template<typename Op> |
| void ByteCodeParser::parseGetById(const Instruction* currentInstruction) |
| { |
| auto bytecode = currentInstruction->as<Op>(); |
| SpeculatedType prediction = getPrediction(); |
| |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| |
| AccessType type = AccessType::GetById; |
| unsigned opcodeLength = currentInstruction->size(); |
| if (Op::opcodeID == op_try_get_by_id) |
| type = AccessType::TryGetById; |
| else if (Op::opcodeID == op_get_by_id_direct) |
| type = AccessType::GetByIdDirect; |
| |
| GetByStatus getByStatus = GetByStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, |
| m_inlineStackTop->m_baselineMap, m_icContextStack, |
| currentCodeOrigin()); |
| |
| handleGetById( |
| bytecode.m_dst, prediction, base, identifierNumber, getByStatus, type, opcodeLength); |
| } |
| |
| static uint64_t makeDynamicVarOpInfo(unsigned identifierNumber, unsigned getPutInfo) |
| { |
| static_assert(sizeof(identifierNumber) == 4, |
| "We cannot fit identifierNumber into the high bits of m_opInfo"); |
| return static_cast<uint64_t>(identifierNumber) | (static_cast<uint64_t>(getPutInfo) << 32); |
| } |
| |
| // The idiom: |
| // if (true) { ...; goto label; } else label: continue |
| // Allows using NEXT_OPCODE as a statement, even in unbraced if+else, while containing a `continue`. |
| // The more common idiom: |
| // do { ...; } while (false) |
| // Doesn't allow using `continue`. |
| #define NEXT_OPCODE(name) \ |
| if (true) { \ |
| m_currentIndex = BytecodeIndex(m_currentIndex.offset() + currentInstruction->size()); \ |
| goto WTF_CONCAT(NEXT_OPCODE_, __LINE__); /* Need a unique label: usable more than once per function. */ \ |
| } else \ |
| WTF_CONCAT(NEXT_OPCODE_, __LINE__): \ |
| continue |
| |
| #define LAST_OPCODE_LINKED(name) do { \ |
| m_currentIndex = BytecodeIndex(m_currentIndex.offset() + currentInstruction->size()); \ |
| m_exitOK = false; \ |
| return; \ |
| } while (false) |
| |
| #define LAST_OPCODE(name) \ |
| do { \ |
| if (m_currentBlock->terminal()) { \ |
| switch (m_currentBlock->terminal()->op()) { \ |
| case Jump: \ |
| case Branch: \ |
| case Switch: \ |
| ASSERT(!m_currentBlock->isLinked); \ |
| m_inlineStackTop->m_unlinkedBlocks.append(m_currentBlock); \ |
| break;\ |
| default: break; \ |
| } \ |
| } \ |
| LAST_OPCODE_LINKED(name); \ |
| } while (false) |
| |
| void ByteCodeParser::parseBlock(unsigned limit) |
| { |
| auto& instructions = m_inlineStackTop->m_codeBlock->instructions(); |
| BytecodeIndex blockBegin = m_currentIndex; |
| |
| // If we are the first basic block, introduce markers for arguments. This allows |
| // us to track if a use of an argument may use the actual argument passed, as |
| // opposed to using a value we set explicitly. |
| if (m_currentBlock == m_graph.block(0) && !inlineCallFrame()) { |
| auto addResult = m_graph.m_rootToArguments.add(m_currentBlock, ArgumentsVector()); |
| RELEASE_ASSERT(addResult.isNewEntry); |
| ArgumentsVector& entrypointArguments = addResult.iterator->value; |
| entrypointArguments.resize(m_numArguments); |
| |
| // We will emit SetArgumentDefinitely nodes. They don't exit, but we're at the top of an op_enter so |
| // exitOK = true. |
| m_exitOK = true; |
| for (unsigned argument = 0; argument < m_numArguments; ++argument) { |
| VariableAccessData* variable = newVariableAccessData( |
| virtualRegisterForArgument(argument)); |
| variable->mergeStructureCheckHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache)); |
| variable->mergeCheckArrayHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType)); |
| |
| Node* setArgument = addToGraph(SetArgumentDefinitely, OpInfo(variable)); |
| entrypointArguments[argument] = setArgument; |
| m_currentBlock->variablesAtTail.setArgumentFirstTime(argument, setArgument); |
| } |
| } |
| |
| CodeBlock* codeBlock = m_inlineStackTop->m_codeBlock; |
| |
| auto jumpTarget = [&](int target) { |
| if (target) |
| return target; |
| return codeBlock->outOfLineJumpOffset(m_currentInstruction); |
| }; |
| |
| while (true) { |
| // We're staring at a new bytecode instruction. So we once again have a place that we can exit |
| // to. |
| m_exitOK = true; |
| |
| processSetLocalQueue(); |
| |
| // Don't extend over jump destinations. |
| if (m_currentIndex.offset() == limit) { |
| // Ordinarily we want to plant a jump. But refuse to do this if the block is |
| // empty. This is a special case for inlining, which might otherwise create |
| // some empty blocks in some cases. When parseBlock() returns with an empty |
| // block, it will get repurposed instead of creating a new one. Note that this |
| // logic relies on every bytecode resulting in one or more nodes, which would |
| // be true anyway except for op_loop_hint, which emits a Phantom to force this |
| // to be true. |
| |
| if (!m_currentBlock->isEmpty()) |
| addJumpTo(m_currentIndex.offset()); |
| return; |
| } |
| |
| // Switch on the current bytecode opcode. |
| const Instruction* currentInstruction = instructions.at(m_currentIndex).ptr(); |
| m_currentInstruction = currentInstruction; // Some methods want to use this, and we'd rather not thread it through calls. |
| OpcodeID opcodeID = currentInstruction->opcodeID(); |
| |
| VERBOSE_LOG(" parsing ", currentCodeOrigin(), ": ", opcodeID, "\n"); |
| |
| if (UNLIKELY(m_graph.compilation())) { |
| addToGraph(CountExecution, OpInfo(m_graph.compilation()->executionCounterFor( |
| Profiler::OriginStack(*m_vm->m_perBytecodeProfiler, m_codeBlock, currentCodeOrigin())))); |
| } |
| |
| switch (opcodeID) { |
| |
| // === Function entry opcodes === |
| |
| case op_enter: { |
| Node* undefined = addToGraph(JSConstant, OpInfo(m_constantUndefined)); |
| // Initialize all locals to undefined. |
| for (int i = 0; i < m_inlineStackTop->m_codeBlock->numVars(); ++i) |
| set(virtualRegisterForLocal(i), undefined, ImmediateNakedSet); |
| |
| NEXT_OPCODE(op_enter); |
| } |
| |
| case op_to_this: { |
| Node* op1 = getThis(); |
| auto& metadata = currentInstruction->as<OpToThis>().metadata(codeBlock); |
| StructureID cachedStructureID = metadata.m_cachedStructureID; |
| Structure* cachedStructure = nullptr; |
| if (cachedStructureID) |
| cachedStructure = m_vm->heap.structureIDTable().get(cachedStructureID); |
| if (metadata.m_toThisStatus != ToThisOK |
| || !cachedStructure |
| || cachedStructure->classInfo()->methodTable.toThis != JSObject::info()->methodTable.toThis |
| || m_inlineStackTop->m_profiledBlock->couldTakeSlowCase(m_currentIndex) |
| || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache) |
| || (op1->op() == GetLocal && op1->variableAccessData()->structureCheckHoistingFailed())) { |
| setThis(addToGraph(ToThis, OpInfo(), OpInfo(getPrediction()), op1)); |
| } else { |
| addToGraph( |
| CheckStructure, |
| OpInfo(m_graph.addStructureSet(cachedStructure)), |
| op1); |
| } |
| NEXT_OPCODE(op_to_this); |
| } |
| |
| case op_create_this: { |
| auto bytecode = currentInstruction->as<OpCreateThis>(); |
| Node* callee = get(VirtualRegister(bytecode.m_callee)); |
| |
| JSFunction* function = callee->dynamicCastConstant<JSFunction*>(*m_vm); |
| if (!function) { |
| JSCell* cachedFunction = bytecode.metadata(codeBlock).m_cachedCallee.unvalidatedGet(); |
| if (cachedFunction |
| && cachedFunction != JSCell::seenMultipleCalleeObjects() |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) { |
| ASSERT(cachedFunction->inherits<JSFunction>(*m_vm)); |
| |
| FrozenValue* frozen = m_graph.freeze(cachedFunction); |
| addToGraph(CheckCell, OpInfo(frozen), callee); |
| |
| function = static_cast<JSFunction*>(cachedFunction); |
| } |
| } |
| |
| bool alreadyEmitted = false; |
| if (function) { |
| if (FunctionRareData* rareData = function->rareData()) { |
| if (rareData->allocationProfileWatchpointSet().isStillValid()) { |
| Structure* structure = rareData->objectAllocationStructure(); |
| JSObject* prototype = rareData->objectAllocationPrototype(); |
| if (structure |
| && (structure->hasMonoProto() || prototype) |
| && rareData->allocationProfileWatchpointSet().isStillValid()) { |
| |
| m_graph.freeze(rareData); |
| m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); |
| |
| Node* object = addToGraph(NewObject, OpInfo(m_graph.registerStructure(structure))); |
| if (structure->hasPolyProto()) { |
| StorageAccessData* data = m_graph.m_storageAccessData.add(); |
| data->offset = knownPolyProtoOffset; |
| data->identifierNumber = m_graph.identifiers().ensure(m_graph.m_vm.propertyNames->builtinNames().polyProtoName().impl()); |
| ASSERT(isInlineOffset(knownPolyProtoOffset)); |
| addToGraph(PutByOffset, OpInfo(data), object, object, weakJSConstant(prototype)); |
| } |
| set(VirtualRegister(bytecode.m_dst), object); |
| // The callee is still live up to this point. |
| addToGraph(Phantom, callee); |
| alreadyEmitted = true; |
| } |
| } |
| } |
| } |
| if (!alreadyEmitted) { |
| set(VirtualRegister(bytecode.m_dst), |
| addToGraph(CreateThis, OpInfo(bytecode.m_inlineCapacity), callee)); |
| } |
| NEXT_OPCODE(op_create_this); |
| } |
| |
| case op_create_promise: { |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| auto bytecode = currentInstruction->as<OpCreatePromise>(); |
| Node* callee = get(VirtualRegister(bytecode.m_callee)); |
| |
| bool alreadyEmitted = false; |
| |
| { |
| // Attempt to convert to NewPromise first in easy case. |
| JSPromiseConstructor* promiseConstructor = callee->dynamicCastConstant<JSPromiseConstructor*>(*m_vm); |
| if (promiseConstructor == (bytecode.m_isInternalPromise ? globalObject->internalPromiseConstructor() : globalObject->promiseConstructor())) { |
| JSCell* cachedFunction = bytecode.metadata(codeBlock).m_cachedCallee.unvalidatedGet(); |
| if (cachedFunction |
| && cachedFunction != JSCell::seenMultipleCalleeObjects() |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell) |
| && cachedFunction == (bytecode.m_isInternalPromise ? globalObject->internalPromiseConstructor() : globalObject->promiseConstructor())) { |
| FrozenValue* frozen = m_graph.freeze(cachedFunction); |
| addToGraph(CheckCell, OpInfo(frozen), callee); |
| |
| promiseConstructor = jsCast<JSPromiseConstructor*>(cachedFunction); |
| } |
| } |
| if (promiseConstructor) { |
| addToGraph(Phantom, callee); |
| set(VirtualRegister(bytecode.m_dst), addToGraph(NewPromise, OpInfo(m_graph.registerStructure(bytecode.m_isInternalPromise ? globalObject->internalPromiseStructure() : globalObject->promiseStructure())), OpInfo(bytecode.m_isInternalPromise))); |
| alreadyEmitted = true; |
| } |
| } |
| |
| // Derived function case. |
| if (!alreadyEmitted) { |
| JSFunction* function = callee->dynamicCastConstant<JSFunction*>(*m_vm); |
| if (!function) { |
| JSCell* cachedFunction = bytecode.metadata(codeBlock).m_cachedCallee.unvalidatedGet(); |
| if (cachedFunction |
| && cachedFunction != JSCell::seenMultipleCalleeObjects() |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) { |
| ASSERT(cachedFunction->inherits<JSFunction>(*m_vm)); |
| |
| FrozenValue* frozen = m_graph.freeze(cachedFunction); |
| addToGraph(CheckCell, OpInfo(frozen), callee); |
| |
| function = static_cast<JSFunction*>(cachedFunction); |
| } |
| } |
| |
| if (function) { |
| if (FunctionRareData* rareData = function->rareData()) { |
| if (rareData->allocationProfileWatchpointSet().isStillValid()) { |
| Structure* structure = rareData->internalFunctionAllocationStructure(); |
| if (structure |
| && structure->classInfo() == (bytecode.m_isInternalPromise ? JSInternalPromise::info() : JSPromise::info()) |
| && structure->globalObject() == globalObject |
| && rareData->allocationProfileWatchpointSet().isStillValid()) { |
| m_graph.freeze(rareData); |
| m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); |
| |
| set(VirtualRegister(bytecode.m_dst), addToGraph(NewPromise, OpInfo(m_graph.registerStructure(structure)), OpInfo(bytecode.m_isInternalPromise))); |
| // The callee is still live up to this point. |
| addToGraph(Phantom, callee); |
| alreadyEmitted = true; |
| } |
| } |
| } |
| } |
| if (!alreadyEmitted) |
| set(VirtualRegister(bytecode.m_dst), addToGraph(CreatePromise, OpInfo(), OpInfo(bytecode.m_isInternalPromise), callee)); |
| } |
| NEXT_OPCODE(op_create_promise); |
| } |
| |
| case op_create_generator: { |
| handleCreateInternalFieldObject(JSGenerator::info(), CreateGenerator, NewGenerator, currentInstruction->as<OpCreateGenerator>()); |
| NEXT_OPCODE(op_create_generator); |
| } |
| |
| case op_create_async_generator: { |
| handleCreateInternalFieldObject(JSAsyncGenerator::info(), CreateAsyncGenerator, NewAsyncGenerator, currentInstruction->as<OpCreateAsyncGenerator>()); |
| NEXT_OPCODE(op_create_async_generator); |
| } |
| |
| case op_new_object: { |
| auto bytecode = currentInstruction->as<OpNewObject>(); |
| set(bytecode.m_dst, |
| addToGraph(NewObject, |
| OpInfo(m_graph.registerStructure(bytecode.metadata(codeBlock).m_objectAllocationProfile.structure())))); |
| NEXT_OPCODE(op_new_object); |
| } |
| |
| case op_new_promise: { |
| auto bytecode = currentInstruction->as<OpNewPromise>(); |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| set(bytecode.m_dst, addToGraph(NewPromise, OpInfo(m_graph.registerStructure(bytecode.m_isInternalPromise ? globalObject->internalPromiseStructure() : globalObject->promiseStructure())), OpInfo(bytecode.m_isInternalPromise))); |
| NEXT_OPCODE(op_new_promise); |
| } |
| |
| case op_new_generator: { |
| auto bytecode = currentInstruction->as<OpNewGenerator>(); |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| set(bytecode.m_dst, addToGraph(NewGenerator, OpInfo(m_graph.registerStructure(globalObject->generatorStructure())))); |
| NEXT_OPCODE(op_new_generator); |
| } |
| |
| case op_new_array: { |
| auto bytecode = currentInstruction->as<OpNewArray>(); |
| int startOperand = bytecode.m_argv.offset(); |
| int numOperands = bytecode.m_argc; |
| ArrayAllocationProfile& profile = bytecode.metadata(codeBlock).m_arrayAllocationProfile; |
| for (int operandIdx = startOperand; operandIdx > startOperand - numOperands; --operandIdx) |
| addVarArgChild(get(VirtualRegister(operandIdx))); |
| unsigned vectorLengthHint = std::max<unsigned>(profile.vectorLengthHintConcurrently(), numOperands); |
| set(bytecode.m_dst, addToGraph(Node::VarArg, NewArray, OpInfo(profile.selectIndexingTypeConcurrently()), OpInfo(vectorLengthHint))); |
| NEXT_OPCODE(op_new_array); |
| } |
| |
| case op_new_array_with_spread: { |
| auto bytecode = currentInstruction->as<OpNewArrayWithSpread>(); |
| int startOperand = bytecode.m_argv.offset(); |
| int numOperands = bytecode.m_argc; |
| const BitVector& bitVector = m_inlineStackTop->m_profiledBlock->unlinkedCodeBlock()->bitVector(bytecode.m_bitVector); |
| for (int operandIdx = startOperand; operandIdx > startOperand - numOperands; --operandIdx) |
| addVarArgChild(get(VirtualRegister(operandIdx))); |
| |
| BitVector* copy = m_graph.m_bitVectors.add(bitVector); |
| ASSERT(*copy == bitVector); |
| |
| set(bytecode.m_dst, |
| addToGraph(Node::VarArg, NewArrayWithSpread, OpInfo(copy))); |
| NEXT_OPCODE(op_new_array_with_spread); |
| } |
| |
| case op_spread: { |
| auto bytecode = currentInstruction->as<OpSpread>(); |
| set(bytecode.m_dst, |
| addToGraph(Spread, get(bytecode.m_argument))); |
| NEXT_OPCODE(op_spread); |
| } |
| |
| case op_new_array_with_size: { |
| auto bytecode = currentInstruction->as<OpNewArrayWithSize>(); |
| ArrayAllocationProfile& profile = bytecode.metadata(codeBlock).m_arrayAllocationProfile; |
| set(bytecode.m_dst, addToGraph(NewArrayWithSize, OpInfo(profile.selectIndexingTypeConcurrently()), get(bytecode.m_length))); |
| NEXT_OPCODE(op_new_array_with_size); |
| } |
| |
| case op_new_array_buffer: { |
| auto bytecode = currentInstruction->as<OpNewArrayBuffer>(); |
| // Unfortunately, we can't allocate a new JSImmutableButterfly if the profile tells us new information because we |
| // cannot allocate from compilation threads. |
| WTF::loadLoadFence(); |
| FrozenValue* frozen = get(VirtualRegister(bytecode.m_immutableButterfly))->constant(); |
| WTF::loadLoadFence(); |
| JSImmutableButterfly* immutableButterfly = frozen->cast<JSImmutableButterfly*>(); |
| NewArrayBufferData data { }; |
| data.indexingMode = immutableButterfly->indexingMode(); |
| data.vectorLengthHint = immutableButterfly->toButterfly()->vectorLength(); |
| |
| set(VirtualRegister(bytecode.m_dst), addToGraph(NewArrayBuffer, OpInfo(frozen), OpInfo(data.asQuadWord))); |
| NEXT_OPCODE(op_new_array_buffer); |
| } |
| |
| case op_new_regexp: { |
| auto bytecode = currentInstruction->as<OpNewRegexp>(); |
| ASSERT(bytecode.m_regexp.isConstant()); |
| FrozenValue* frozenRegExp = m_graph.freezeStrong(m_inlineStackTop->m_codeBlock->getConstant(bytecode.m_regexp)); |
| set(bytecode.m_dst, addToGraph(NewRegexp, OpInfo(frozenRegExp), jsConstant(jsNumber(0)))); |
| NEXT_OPCODE(op_new_regexp); |
| } |
| |
| case op_get_rest_length: { |
| auto bytecode = currentInstruction->as<OpGetRestLength>(); |
| InlineCallFrame* inlineCallFrame = this->inlineCallFrame(); |
| Node* length; |
| if (inlineCallFrame && !inlineCallFrame->isVarargs()) { |
| unsigned argumentsLength = inlineCallFrame->argumentCountIncludingThis - 1; |
| JSValue restLength; |
| if (argumentsLength <= bytecode.m_numParametersToSkip) |
| restLength = jsNumber(0); |
| else |
| restLength = jsNumber(argumentsLength - bytecode.m_numParametersToSkip); |
| |
| length = jsConstant(restLength); |
| } else |
| length = addToGraph(GetRestLength, OpInfo(bytecode.m_numParametersToSkip)); |
| set(bytecode.m_dst, length); |
| NEXT_OPCODE(op_get_rest_length); |
| } |
| |
| case op_create_rest: { |
| auto bytecode = currentInstruction->as<OpCreateRest>(); |
| noticeArgumentsUse(); |
| Node* arrayLength = get(bytecode.m_arraySize); |
| set(bytecode.m_dst, |
| addToGraph(CreateRest, OpInfo(bytecode.m_numParametersToSkip), arrayLength)); |
| NEXT_OPCODE(op_create_rest); |
| } |
| |
| // === Bitwise operations === |
| |
| case op_bitnot: { |
| auto bytecode = currentInstruction->as<OpBitnot>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* op1 = get(bytecode.m_operand); |
| if (op1->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitNot, op1)); |
| else |
| set(bytecode.m_dst, addToGraph(ValueBitNot, OpInfo(), OpInfo(prediction), op1)); |
| NEXT_OPCODE(op_bitnot); |
| } |
| |
| case op_bitand: { |
| auto bytecode = currentInstruction->as<OpBitand>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitAnd, op1, op2)); |
| else |
| set(bytecode.m_dst, addToGraph(ValueBitAnd, OpInfo(), OpInfo(prediction), op1, op2)); |
| NEXT_OPCODE(op_bitand); |
| } |
| |
| case op_bitor: { |
| auto bytecode = currentInstruction->as<OpBitor>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitOr, op1, op2)); |
| else |
| set(bytecode.m_dst, addToGraph(ValueBitOr, OpInfo(), OpInfo(prediction), op1, op2)); |
| NEXT_OPCODE(op_bitor); |
| } |
| |
| case op_bitxor: { |
| auto bytecode = currentInstruction->as<OpBitxor>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitXor, op1, op2)); |
| else |
| set(bytecode.m_dst, addToGraph(ValueBitXor, OpInfo(), OpInfo(prediction), op1, op2)); |
| NEXT_OPCODE(op_bitxor); |
| } |
| |
| case op_rshift: { |
| auto bytecode = currentInstruction->as<OpRshift>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitRShift, op1, op2)); |
| else { |
| SpeculatedType prediction = getPredictionWithoutOSRExit(); |
| set(bytecode.m_dst, addToGraph(ValueBitRShift, OpInfo(), OpInfo(prediction), op1, op2)); |
| } |
| NEXT_OPCODE(op_rshift); |
| } |
| |
| case op_lshift: { |
| auto bytecode = currentInstruction->as<OpLshift>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithBitLShift, op1, op2)); |
| else { |
| SpeculatedType prediction = getPredictionWithoutOSRExit(); |
| set(bytecode.m_dst, addToGraph(ValueBitLShift, OpInfo(), OpInfo(prediction), op1, op2)); |
| } |
| NEXT_OPCODE(op_lshift); |
| } |
| |
| case op_urshift: { |
| auto bytecode = currentInstruction->as<OpUrshift>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(BitURShift, op1, op2)); |
| NEXT_OPCODE(op_urshift); |
| } |
| |
| case op_unsigned: { |
| auto bytecode = currentInstruction->as<OpUnsigned>(); |
| set(bytecode.m_dst, makeSafe(addToGraph(UInt32ToNumber, get(bytecode.m_operand)))); |
| NEXT_OPCODE(op_unsigned); |
| } |
| |
| // === Increment/Decrement opcodes === |
| |
| case op_inc: { |
| auto bytecode = currentInstruction->as<OpInc>(); |
| Node* op = get(bytecode.m_srcDst); |
| // FIXME: we can replace the Inc by either ArithAdd with m_constantOne or ArithAdd with the equivalent BigInt in many cases. |
| // For now we only do so in DFGFixupPhase. |
| // We could probably do it earlier in some cases, but it is not clearly worth the trouble. |
| set(bytecode.m_srcDst, makeSafe(addToGraph(Inc, op))); |
| NEXT_OPCODE(op_inc); |
| } |
| |
| case op_dec: { |
| auto bytecode = currentInstruction->as<OpDec>(); |
| Node* op = get(bytecode.m_srcDst); |
| // FIXME: we can replace the Inc by either ArithSub with m_constantOne or ArithSub with the equivalent BigInt in many cases. |
| // For now we only do so in DFGFixupPhase. |
| // We could probably do it earlier in some cases, but it is not clearly worth the trouble. |
| set(bytecode.m_srcDst, makeSafe(addToGraph(Dec, op))); |
| NEXT_OPCODE(op_dec); |
| } |
| |
| // === Arithmetic operations === |
| |
| case op_add: { |
| auto bytecode = currentInstruction->as<OpAdd>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberResult() && op2->hasNumberResult()) |
| set(bytecode.m_dst, makeSafe(addToGraph(ArithAdd, op1, op2))); |
| else |
| set(bytecode.m_dst, makeSafe(addToGraph(ValueAdd, op1, op2))); |
| NEXT_OPCODE(op_add); |
| } |
| |
| case op_sub: { |
| auto bytecode = currentInstruction->as<OpSub>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberResult() && op2->hasNumberResult()) |
| set(bytecode.m_dst, makeSafe(addToGraph(ArithSub, op1, op2))); |
| else |
| set(bytecode.m_dst, makeSafe(addToGraph(ValueSub, op1, op2))); |
| NEXT_OPCODE(op_sub); |
| } |
| |
| case op_negate: { |
| auto bytecode = currentInstruction->as<OpNegate>(); |
| Node* op1 = get(bytecode.m_operand); |
| if (op1->hasNumberResult()) |
| set(bytecode.m_dst, makeSafe(addToGraph(ArithNegate, op1))); |
| else |
| set(bytecode.m_dst, makeSafe(addToGraph(ValueNegate, op1))); |
| NEXT_OPCODE(op_negate); |
| } |
| |
| case op_mul: { |
| // Multiply requires that the inputs are not truncated, unfortunately. |
| auto bytecode = currentInstruction->as<OpMul>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberResult() && op2->hasNumberResult()) |
| set(bytecode.m_dst, makeSafe(addToGraph(ArithMul, op1, op2))); |
| else |
| set(bytecode.m_dst, makeSafe(addToGraph(ValueMul, op1, op2))); |
| NEXT_OPCODE(op_mul); |
| } |
| |
| case op_mod: { |
| auto bytecode = currentInstruction->as<OpMod>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberResult() && op2->hasNumberResult()) |
| set(bytecode.m_dst, makeSafe(addToGraph(ArithMod, op1, op2))); |
| else |
| set(bytecode.m_dst, makeSafe(addToGraph(ValueMod, op1, op2))); |
| NEXT_OPCODE(op_mod); |
| } |
| |
| case op_pow: { |
| auto bytecode = currentInstruction->as<OpPow>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberOrAnyIntResult() && op2->hasNumberOrAnyIntResult()) |
| set(bytecode.m_dst, addToGraph(ArithPow, op1, op2)); |
| else |
| set(bytecode.m_dst, addToGraph(ValuePow, op1, op2)); |
| NEXT_OPCODE(op_pow); |
| } |
| |
| case op_div: { |
| auto bytecode = currentInstruction->as<OpDiv>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| if (op1->hasNumberResult() && op2->hasNumberResult()) |
| set(bytecode.m_dst, makeDivSafe(addToGraph(ArithDiv, op1, op2))); |
| else |
| set(bytecode.m_dst, makeDivSafe(addToGraph(ValueDiv, op1, op2))); |
| NEXT_OPCODE(op_div); |
| } |
| |
| // === Misc operations === |
| |
| case op_debug: { |
| // This is a nop in the DFG/FTL because when we set a breakpoint in the debugger, |
| // we will jettison all optimized CodeBlocks that contains the breakpoint. |
| addToGraph(Check); // We add a nop here so that basic block linking doesn't break. |
| NEXT_OPCODE(op_debug); |
| } |
| |
| case op_mov: { |
| auto bytecode = currentInstruction->as<OpMov>(); |
| Node* op = get(bytecode.m_src); |
| set(bytecode.m_dst, op); |
| NEXT_OPCODE(op_mov); |
| } |
| |
| case op_check_tdz: { |
| auto bytecode = currentInstruction->as<OpCheckTdz>(); |
| addToGraph(CheckNotEmpty, get(bytecode.m_targetVirtualRegister)); |
| NEXT_OPCODE(op_check_tdz); |
| } |
| |
| case op_overrides_has_instance: { |
| auto bytecode = currentInstruction->as<OpOverridesHasInstance>(); |
| JSFunction* defaultHasInstanceSymbolFunction = m_inlineStackTop->m_codeBlock->globalObjectFor(currentCodeOrigin())->functionProtoHasInstanceSymbolFunction(); |
| |
| Node* constructor = get(VirtualRegister(bytecode.m_constructor)); |
| Node* hasInstanceValue = get(VirtualRegister(bytecode.m_hasInstanceValue)); |
| |
| set(VirtualRegister(bytecode.m_dst), addToGraph(OverridesHasInstance, OpInfo(m_graph.freeze(defaultHasInstanceSymbolFunction)), constructor, hasInstanceValue)); |
| NEXT_OPCODE(op_overrides_has_instance); |
| } |
| |
| case op_identity_with_profile: { |
| auto bytecode = currentInstruction->as<OpIdentityWithProfile>(); |
| Node* srcDst = get(bytecode.m_srcDst); |
| SpeculatedType speculation = static_cast<SpeculatedType>(bytecode.m_topProfile) << 32 | static_cast<SpeculatedType>(bytecode.m_bottomProfile); |
| set(bytecode.m_srcDst, addToGraph(IdentityWithProfile, OpInfo(speculation), srcDst)); |
| NEXT_OPCODE(op_identity_with_profile); |
| } |
| |
| case op_instanceof: { |
| auto bytecode = currentInstruction->as<OpInstanceof>(); |
| |
| InstanceOfStatus status = InstanceOfStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, m_inlineStackTop->m_baselineMap, |
| m_currentIndex); |
| |
| Node* value = get(bytecode.m_value); |
| Node* prototype = get(bytecode.m_prototype); |
| |
| // Only inline it if it's Simple with a commonPrototype; bottom/top or variable |
| // prototypes both get handled by the IC. This makes sense for bottom (unprofiled) |
| // instanceof ICs because the profit of this optimization is fairly low. So, in the |
| // absence of any information, it's better to avoid making this be the cause of a |
| // recompilation. |
| if (JSObject* commonPrototype = status.commonPrototype()) { |
| addToGraph(CheckCell, OpInfo(m_graph.freeze(commonPrototype)), prototype); |
| |
| bool allOK = true; |
| MatchStructureData* data = m_graph.m_matchStructureData.add(); |
| for (const InstanceOfVariant& variant : status.variants()) { |
| if (!check(variant.conditionSet())) { |
| allOK = false; |
| break; |
| } |
| for (Structure* structure : variant.structureSet()) { |
| MatchStructureVariant matchVariant; |
| matchVariant.structure = m_graph.registerStructure(structure); |
| matchVariant.result = variant.isHit(); |
| |
| data->variants.append(WTFMove(matchVariant)); |
| } |
| } |
| |
| if (allOK) { |
| Node* match = addToGraph(MatchStructure, OpInfo(data), value); |
| set(bytecode.m_dst, match); |
| NEXT_OPCODE(op_instanceof); |
| } |
| } |
| |
| set(bytecode.m_dst, addToGraph(InstanceOf, value, prototype)); |
| NEXT_OPCODE(op_instanceof); |
| } |
| |
| case op_instanceof_custom: { |
| auto bytecode = currentInstruction->as<OpInstanceofCustom>(); |
| Node* value = get(bytecode.m_value); |
| Node* constructor = get(bytecode.m_constructor); |
| Node* hasInstanceValue = get(bytecode.m_hasInstanceValue); |
| set(bytecode.m_dst, addToGraph(InstanceOfCustom, value, constructor, hasInstanceValue)); |
| NEXT_OPCODE(op_instanceof_custom); |
| } |
| case op_is_empty: { |
| auto bytecode = currentInstruction->as<OpIsEmpty>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsEmpty, value)); |
| NEXT_OPCODE(op_is_empty); |
| } |
| case op_is_undefined: { |
| auto bytecode = currentInstruction->as<OpIsUndefined>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsUndefined, value)); |
| NEXT_OPCODE(op_is_undefined); |
| } |
| case op_is_undefined_or_null: { |
| auto bytecode = currentInstruction->as<OpIsUndefinedOrNull>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsUndefinedOrNull, value)); |
| NEXT_OPCODE(op_is_undefined_or_null); |
| } |
| |
| case op_is_boolean: { |
| auto bytecode = currentInstruction->as<OpIsBoolean>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsBoolean, value)); |
| NEXT_OPCODE(op_is_boolean); |
| } |
| |
| case op_is_number: { |
| auto bytecode = currentInstruction->as<OpIsNumber>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsNumber, value)); |
| NEXT_OPCODE(op_is_number); |
| } |
| |
| case op_is_cell_with_type: { |
| auto bytecode = currentInstruction->as<OpIsCellWithType>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsCellWithType, OpInfo(bytecode.m_type), value)); |
| NEXT_OPCODE(op_is_cell_with_type); |
| } |
| |
| case op_is_object: { |
| auto bytecode = currentInstruction->as<OpIsObject>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsObject, value)); |
| NEXT_OPCODE(op_is_object); |
| } |
| |
| case op_is_object_or_null: { |
| auto bytecode = currentInstruction->as<OpIsObjectOrNull>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsObjectOrNull, value)); |
| NEXT_OPCODE(op_is_object_or_null); |
| } |
| |
| case op_is_function: { |
| auto bytecode = currentInstruction->as<OpIsFunction>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(IsFunction, value)); |
| NEXT_OPCODE(op_is_function); |
| } |
| |
| case op_not: { |
| auto bytecode = currentInstruction->as<OpNot>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(LogicalNot, value)); |
| NEXT_OPCODE(op_not); |
| } |
| |
| case op_to_primitive: { |
| auto bytecode = currentInstruction->as<OpToPrimitive>(); |
| Node* value = get(bytecode.m_src); |
| set(bytecode.m_dst, addToGraph(ToPrimitive, value)); |
| NEXT_OPCODE(op_to_primitive); |
| } |
| |
| case op_strcat: { |
| auto bytecode = currentInstruction->as<OpStrcat>(); |
| int startOperand = bytecode.m_src.offset(); |
| int numOperands = bytecode.m_count; |
| const unsigned maxArguments = 3; |
| Node* operands[AdjacencyList::Size]; |
| unsigned indexInOperands = 0; |
| for (unsigned i = 0; i < AdjacencyList::Size; ++i) |
| operands[i] = 0; |
| for (int operandIdx = 0; operandIdx < numOperands; ++operandIdx) { |
| if (indexInOperands == maxArguments) { |
| operands[0] = addToGraph(StrCat, operands[0], operands[1], operands[2]); |
| for (unsigned i = 1; i < AdjacencyList::Size; ++i) |
| operands[i] = 0; |
| indexInOperands = 1; |
| } |
| |
| ASSERT(indexInOperands < AdjacencyList::Size); |
| ASSERT(indexInOperands < maxArguments); |
| operands[indexInOperands++] = get(VirtualRegister(startOperand - operandIdx)); |
| } |
| set(bytecode.m_dst, addToGraph(StrCat, operands[0], operands[1], operands[2])); |
| NEXT_OPCODE(op_strcat); |
| } |
| |
| case op_less: { |
| auto bytecode = currentInstruction->as<OpLess>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareLess, op1, op2)); |
| NEXT_OPCODE(op_less); |
| } |
| |
| case op_lesseq: { |
| auto bytecode = currentInstruction->as<OpLesseq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareLessEq, op1, op2)); |
| NEXT_OPCODE(op_lesseq); |
| } |
| |
| case op_greater: { |
| auto bytecode = currentInstruction->as<OpGreater>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareGreater, op1, op2)); |
| NEXT_OPCODE(op_greater); |
| } |
| |
| case op_greatereq: { |
| auto bytecode = currentInstruction->as<OpGreatereq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareGreaterEq, op1, op2)); |
| NEXT_OPCODE(op_greatereq); |
| } |
| |
| case op_below: { |
| auto bytecode = currentInstruction->as<OpBelow>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareBelow, op1, op2)); |
| NEXT_OPCODE(op_below); |
| } |
| |
| case op_beloweq: { |
| auto bytecode = currentInstruction->as<OpBeloweq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareBelowEq, op1, op2)); |
| NEXT_OPCODE(op_beloweq); |
| } |
| |
| case op_eq: { |
| auto bytecode = currentInstruction->as<OpEq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareEq, op1, op2)); |
| NEXT_OPCODE(op_eq); |
| } |
| |
| case op_eq_null: { |
| auto bytecode = currentInstruction->as<OpEqNull>(); |
| Node* value = get(bytecode.m_operand); |
| Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull)); |
| set(bytecode.m_dst, addToGraph(CompareEq, value, nullConstant)); |
| NEXT_OPCODE(op_eq_null); |
| } |
| |
| case op_stricteq: { |
| auto bytecode = currentInstruction->as<OpStricteq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(CompareStrictEq, op1, op2)); |
| NEXT_OPCODE(op_stricteq); |
| } |
| |
| case op_neq: { |
| auto bytecode = currentInstruction->as<OpNeq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| set(bytecode.m_dst, addToGraph(LogicalNot, addToGraph(CompareEq, op1, op2))); |
| NEXT_OPCODE(op_neq); |
| } |
| |
| case op_neq_null: { |
| auto bytecode = currentInstruction->as<OpNeqNull>(); |
| Node* value = get(bytecode.m_operand); |
| Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull)); |
| set(bytecode.m_dst, addToGraph(LogicalNot, addToGraph(CompareEq, value, nullConstant))); |
| NEXT_OPCODE(op_neq_null); |
| } |
| |
| case op_nstricteq: { |
| auto bytecode = currentInstruction->as<OpNstricteq>(); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* invertedResult; |
| invertedResult = addToGraph(CompareStrictEq, op1, op2); |
| set(bytecode.m_dst, addToGraph(LogicalNot, invertedResult)); |
| NEXT_OPCODE(op_nstricteq); |
| } |
| |
| // === Property access operations === |
| |
| case op_get_by_val: { |
| auto bytecode = currentInstruction->as<OpGetByVal>(); |
| SpeculatedType prediction = getPredictionWithoutOSRExit(); |
| |
| Node* base = get(bytecode.m_base); |
| Node* property = get(bytecode.m_property); |
| bool shouldCompileAsGetById = false; |
| GetByStatus getByStatus = GetByStatus::computeFor(m_inlineStackTop->m_profiledBlock, m_inlineStackTop->m_baselineMap, m_icContextStack, currentCodeOrigin()); |
| |
| unsigned identifierNumber = 0; |
| |
| if (!m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIdent) |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType) |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) { |
| |
| // FIXME: In the future, we should be able to do something like MultiGetByOffset in a multi identifier mode. |
| // That way, we could both switch on multiple structures and multiple identifiers (or int 32 properties). |
| // https://bugs.webkit.org/show_bug.cgi?id=204216 |
| if (CacheableIdentifier identifier = getByStatus.singleIdentifier()) { |
| UniquedStringImpl* uid = identifier.uid(); |
| identifierNumber = m_graph.identifiers().ensure(identifier.uid()); |
| if (identifier.isCell()) { |
| FrozenValue* frozen = m_graph.freezeStrong(identifier.cell()); |
| if (identifier.isSymbolCell()) |
| addToGraph(CheckCell, OpInfo(frozen), property); |
| else |
| addToGraph(CheckIdent, OpInfo(uid), property); |
| } else |
| addToGraph(CheckIdent, OpInfo(uid), property); |
| shouldCompileAsGetById = true; |
| } |
| } |
| |
| if (shouldCompileAsGetById) |
| handleGetById(bytecode.m_dst, prediction, base, identifierNumber, getByStatus, AccessType::GetById, currentInstruction->size()); |
| else { |
| ArrayMode arrayMode = getArrayMode(bytecode.metadata(codeBlock).m_arrayProfile, Array::Read); |
| // FIXME: We could consider making this not vararg, since it only uses three child |
| // slots. |
| // https://bugs.webkit.org/show_bug.cgi?id=184192 |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(0); // Leave room for property storage. |
| Node* getByVal = addToGraph(Node::VarArg, GetByVal, OpInfo(arrayMode.asWord()), OpInfo(prediction)); |
| m_exitOK = false; // GetByVal must be treated as if it clobbers exit state, since FixupPhase may make it generic. |
| set(bytecode.m_dst, getByVal); |
| if (getByStatus.observedStructureStubInfoSlowPath() || bytecode.metadata(codeBlock).m_seenIdentifiers.count() > Options::getByValICMaxNumberOfIdentifiers()) |
| m_graph.m_slowGetByVal.add(getByVal); |
| } |
| |
| NEXT_OPCODE(op_get_by_val); |
| } |
| |
| case op_get_by_val_with_this: { |
| auto bytecode = currentInstruction->as<OpGetByValWithThis>(); |
| SpeculatedType prediction = getPrediction(); |
| |
| Node* base = get(bytecode.m_base); |
| Node* thisValue = get(bytecode.m_thisValue); |
| Node* property = get(bytecode.m_property); |
| Node* getByValWithThis = addToGraph(GetByValWithThis, OpInfo(), OpInfo(prediction), base, thisValue, property); |
| set(bytecode.m_dst, getByValWithThis); |
| |
| NEXT_OPCODE(op_get_by_val_with_this); |
| } |
| |
| case op_put_by_val_direct: |
| handlePutByVal(currentInstruction->as<OpPutByValDirect>(), currentInstruction->size()); |
| NEXT_OPCODE(op_put_by_val_direct); |
| |
| case op_put_by_val: { |
| handlePutByVal(currentInstruction->as<OpPutByVal>(), currentInstruction->size()); |
| NEXT_OPCODE(op_put_by_val); |
| } |
| |
| case op_put_by_val_with_this: { |
| auto bytecode = currentInstruction->as<OpPutByValWithThis>(); |
| Node* base = get(bytecode.m_base); |
| Node* thisValue = get(bytecode.m_thisValue); |
| Node* property = get(bytecode.m_property); |
| Node* value = get(bytecode.m_value); |
| |
| addVarArgChild(base); |
| addVarArgChild(thisValue); |
| addVarArgChild(property); |
| addVarArgChild(value); |
| addToGraph(Node::VarArg, PutByValWithThis, OpInfo(0), OpInfo(0)); |
| |
| NEXT_OPCODE(op_put_by_val_with_this); |
| } |
| |
| case op_define_data_property: { |
| auto bytecode = currentInstruction->as<OpDefineDataProperty>(); |
| Node* base = get(bytecode.m_base); |
| Node* property = get(bytecode.m_property); |
| Node* value = get(bytecode.m_value); |
| Node* attributes = get(bytecode.m_attributes); |
| |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(value); |
| addVarArgChild(attributes); |
| addToGraph(Node::VarArg, DefineDataProperty, OpInfo(0), OpInfo(0)); |
| |
| NEXT_OPCODE(op_define_data_property); |
| } |
| |
| case op_define_accessor_property: { |
| auto bytecode = currentInstruction->as<OpDefineAccessorProperty>(); |
| Node* base = get(bytecode.m_base); |
| Node* property = get(bytecode.m_property); |
| Node* getter = get(bytecode.m_getter); |
| Node* setter = get(bytecode.m_setter); |
| Node* attributes = get(bytecode.m_attributes); |
| |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(getter); |
| addVarArgChild(setter); |
| addVarArgChild(attributes); |
| addToGraph(Node::VarArg, DefineAccessorProperty, OpInfo(0), OpInfo(0)); |
| |
| NEXT_OPCODE(op_define_accessor_property); |
| } |
| |
| case op_get_by_id_direct: { |
| parseGetById<OpGetByIdDirect>(currentInstruction); |
| NEXT_OPCODE(op_get_by_id_direct); |
| } |
| case op_try_get_by_id: { |
| parseGetById<OpTryGetById>(currentInstruction); |
| NEXT_OPCODE(op_try_get_by_id); |
| } |
| case op_get_by_id: { |
| parseGetById<OpGetById>(currentInstruction); |
| NEXT_OPCODE(op_get_by_id); |
| } |
| case op_get_by_id_with_this: { |
| SpeculatedType prediction = getPrediction(); |
| |
| auto bytecode = currentInstruction->as<OpGetByIdWithThis>(); |
| Node* base = get(bytecode.m_base); |
| Node* thisValue = get(bytecode.m_thisValue); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| |
| set(bytecode.m_dst, |
| addToGraph(GetByIdWithThis, OpInfo(identifierNumber), OpInfo(prediction), base, thisValue)); |
| |
| NEXT_OPCODE(op_get_by_id_with_this); |
| } |
| case op_put_by_id: { |
| auto bytecode = currentInstruction->as<OpPutById>(); |
| Node* value = get(bytecode.m_value); |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| bool direct = !!(bytecode.m_flags & PutByIdIsDirect); |
| |
| PutByIdStatus putByIdStatus = PutByIdStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, |
| m_inlineStackTop->m_baselineMap, m_icContextStack, |
| currentCodeOrigin(), m_graph.identifiers()[identifierNumber]); |
| |
| handlePutById(base, identifierNumber, value, putByIdStatus, direct, currentInstruction->size()); |
| NEXT_OPCODE(op_put_by_id); |
| } |
| |
| case op_put_by_id_with_this: { |
| auto bytecode = currentInstruction->as<OpPutByIdWithThis>(); |
| Node* base = get(bytecode.m_base); |
| Node* thisValue = get(bytecode.m_thisValue); |
| Node* value = get(bytecode.m_value); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| |
| addToGraph(PutByIdWithThis, OpInfo(identifierNumber), base, thisValue, value); |
| NEXT_OPCODE(op_put_by_id_with_this); |
| } |
| |
| case op_put_getter_by_id: |
| handlePutAccessorById(PutGetterById, currentInstruction->as<OpPutGetterById>()); |
| NEXT_OPCODE(op_put_getter_by_id); |
| case op_put_setter_by_id: { |
| handlePutAccessorById(PutSetterById, currentInstruction->as<OpPutSetterById>()); |
| NEXT_OPCODE(op_put_setter_by_id); |
| } |
| |
| case op_put_getter_setter_by_id: { |
| auto bytecode = currentInstruction->as<OpPutGetterSetterById>(); |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| Node* getter = get(bytecode.m_getter); |
| Node* setter = get(bytecode.m_setter); |
| addToGraph(PutGetterSetterById, OpInfo(identifierNumber), OpInfo(bytecode.m_attributes), base, getter, setter); |
| NEXT_OPCODE(op_put_getter_setter_by_id); |
| } |
| |
| case op_put_getter_by_val: |
| handlePutAccessorByVal(PutGetterByVal, currentInstruction->as<OpPutGetterByVal>()); |
| NEXT_OPCODE(op_put_getter_by_val); |
| case op_put_setter_by_val: { |
| handlePutAccessorByVal(PutSetterByVal, currentInstruction->as<OpPutSetterByVal>()); |
| NEXT_OPCODE(op_put_setter_by_val); |
| } |
| |
| case op_del_by_id: { |
| auto bytecode = currentInstruction->as<OpDelById>(); |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| set(bytecode.m_dst, addToGraph(DeleteById, OpInfo(identifierNumber), base)); |
| NEXT_OPCODE(op_del_by_id); |
| } |
| |
| case op_del_by_val: { |
| auto bytecode = currentInstruction->as<OpDelByVal>(); |
| Node* base = get(bytecode.m_base); |
| Node* key = get(bytecode.m_property); |
| set(bytecode.m_dst, addToGraph(DeleteByVal, base, key)); |
| NEXT_OPCODE(op_del_by_val); |
| } |
| |
| case op_profile_type: { |
| auto bytecode = currentInstruction->as<OpProfileType>(); |
| auto& metadata = bytecode.metadata(codeBlock); |
| Node* valueToProfile = get(bytecode.m_targetVirtualRegister); |
| addToGraph(ProfileType, OpInfo(metadata.m_typeLocation), valueToProfile); |
| NEXT_OPCODE(op_profile_type); |
| } |
| |
| case op_profile_control_flow: { |
| auto bytecode = currentInstruction->as<OpProfileControlFlow>(); |
| BasicBlockLocation* basicBlockLocation = bytecode.metadata(codeBlock).m_basicBlockLocation; |
| addToGraph(ProfileControlFlow, OpInfo(basicBlockLocation)); |
| NEXT_OPCODE(op_profile_control_flow); |
| } |
| |
| // === Block terminators. === |
| |
| case op_jmp: { |
| ASSERT(!m_currentBlock->terminal()); |
| auto bytecode = currentInstruction->as<OpJmp>(); |
| int relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| addToGraph(Jump, OpInfo(m_currentIndex.offset() + relativeOffset)); |
| if (relativeOffset <= 0) |
| flushForTerminal(); |
| LAST_OPCODE(op_jmp); |
| } |
| |
| case op_jtrue: { |
| auto bytecode = currentInstruction->as<OpJtrue>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* condition = get(bytecode.m_condition); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jtrue); |
| } |
| |
| case op_jfalse: { |
| auto bytecode = currentInstruction->as<OpJfalse>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* condition = get(bytecode.m_condition); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jfalse); |
| } |
| |
| case op_jeq_null: { |
| auto bytecode = currentInstruction->as<OpJeqNull>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* value = get(bytecode.m_value); |
| Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull)); |
| Node* condition = addToGraph(CompareEq, value, nullConstant); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jeq_null); |
| } |
| |
| case op_jneq_null: { |
| auto bytecode = currentInstruction->as<OpJneqNull>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* value = get(bytecode.m_value); |
| Node* nullConstant = addToGraph(JSConstant, OpInfo(m_constantNull)); |
| Node* condition = addToGraph(CompareEq, value, nullConstant); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jneq_null); |
| } |
| |
| case op_jundefined_or_null: { |
| auto bytecode = currentInstruction->as<OpJundefinedOrNull>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* value = get(bytecode.m_value); |
| Node* condition = addToGraph(IsUndefinedOrNull, value); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jundefined_or_null); |
| } |
| |
| case op_jnundefined_or_null: { |
| auto bytecode = currentInstruction->as<OpJnundefinedOrNull>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* value = get(bytecode.m_value); |
| Node* condition = addToGraph(IsUndefinedOrNull, value); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jnundefined_or_null); |
| } |
| |
| case op_jless: { |
| auto bytecode = currentInstruction->as<OpJless>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareLess, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jless); |
| } |
| |
| case op_jlesseq: { |
| auto bytecode = currentInstruction->as<OpJlesseq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareLessEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jlesseq); |
| } |
| |
| case op_jgreater: { |
| auto bytecode = currentInstruction->as<OpJgreater>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareGreater, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jgreater); |
| } |
| |
| case op_jgreatereq: { |
| auto bytecode = currentInstruction->as<OpJgreatereq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareGreaterEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jgreatereq); |
| } |
| |
| case op_jeq: { |
| auto bytecode = currentInstruction->as<OpJeq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jeq); |
| } |
| |
| case op_jstricteq: { |
| auto bytecode = currentInstruction->as<OpJstricteq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareStrictEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jstricteq); |
| } |
| |
| case op_jnless: { |
| auto bytecode = currentInstruction->as<OpJnless>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareLess, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jnless); |
| } |
| |
| case op_jnlesseq: { |
| auto bytecode = currentInstruction->as<OpJnlesseq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareLessEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jnlesseq); |
| } |
| |
| case op_jngreater: { |
| auto bytecode = currentInstruction->as<OpJngreater>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareGreater, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jngreater); |
| } |
| |
| case op_jngreatereq: { |
| auto bytecode = currentInstruction->as<OpJngreatereq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareGreaterEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jngreatereq); |
| } |
| |
| case op_jneq: { |
| auto bytecode = currentInstruction->as<OpJneq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jneq); |
| } |
| |
| case op_jnstricteq: { |
| auto bytecode = currentInstruction->as<OpJnstricteq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareStrictEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jnstricteq); |
| } |
| |
| case op_jbelow: { |
| auto bytecode = currentInstruction->as<OpJbelow>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareBelow, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jbelow); |
| } |
| |
| case op_jbeloweq: { |
| auto bytecode = currentInstruction->as<OpJbeloweq>(); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* op1 = get(bytecode.m_lhs); |
| Node* op2 = get(bytecode.m_rhs); |
| Node* condition = addToGraph(CompareBelowEq, op1, op2); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + relativeOffset, m_currentIndex.offset() + currentInstruction->size())), condition); |
| LAST_OPCODE(op_jbeloweq); |
| } |
| |
| case op_switch_imm: { |
| auto bytecode = currentInstruction->as<OpSwitchImm>(); |
| SwitchData& data = *m_graph.m_switchData.add(); |
| data.kind = SwitchImm; |
| data.switchTableIndex = m_inlineStackTop->m_switchRemap[bytecode.m_tableIndex]; |
| data.fallThrough.setBytecodeIndex(m_currentIndex.offset() + jumpTarget(bytecode.m_defaultOffset)); |
| SimpleJumpTable& table = m_codeBlock->switchJumpTable(data.switchTableIndex); |
| for (unsigned i = 0; i < table.branchOffsets.size(); ++i) { |
| if (!table.branchOffsets[i]) |
| continue; |
| unsigned target = m_currentIndex.offset() + table.branchOffsets[i]; |
| if (target == data.fallThrough.bytecodeIndex()) |
| continue; |
| data.cases.append(SwitchCase::withBytecodeIndex(m_graph.freeze(jsNumber(static_cast<int32_t>(table.min + i))), target)); |
| } |
| addToGraph(Switch, OpInfo(&data), get(bytecode.m_scrutinee)); |
| flushIfTerminal(data); |
| LAST_OPCODE(op_switch_imm); |
| } |
| |
| case op_switch_char: { |
| auto bytecode = currentInstruction->as<OpSwitchChar>(); |
| SwitchData& data = *m_graph.m_switchData.add(); |
| data.kind = SwitchChar; |
| data.switchTableIndex = m_inlineStackTop->m_switchRemap[bytecode.m_tableIndex]; |
| data.fallThrough.setBytecodeIndex(m_currentIndex.offset() + jumpTarget(bytecode.m_defaultOffset)); |
| SimpleJumpTable& table = m_codeBlock->switchJumpTable(data.switchTableIndex); |
| for (unsigned i = 0; i < table.branchOffsets.size(); ++i) { |
| if (!table.branchOffsets[i]) |
| continue; |
| unsigned target = m_currentIndex.offset() + table.branchOffsets[i]; |
| if (target == data.fallThrough.bytecodeIndex()) |
| continue; |
| data.cases.append( |
| SwitchCase::withBytecodeIndex(LazyJSValue::singleCharacterString(table.min + i), target)); |
| } |
| addToGraph(Switch, OpInfo(&data), get(bytecode.m_scrutinee)); |
| flushIfTerminal(data); |
| LAST_OPCODE(op_switch_char); |
| } |
| |
| case op_switch_string: { |
| auto bytecode = currentInstruction->as<OpSwitchString>(); |
| SwitchData& data = *m_graph.m_switchData.add(); |
| data.kind = SwitchString; |
| data.switchTableIndex = bytecode.m_tableIndex; |
| data.fallThrough.setBytecodeIndex(m_currentIndex.offset() + jumpTarget(bytecode.m_defaultOffset)); |
| StringJumpTable& table = m_codeBlock->stringSwitchJumpTable(data.switchTableIndex); |
| StringJumpTable::StringOffsetTable::iterator iter; |
| StringJumpTable::StringOffsetTable::iterator end = table.offsetTable.end(); |
| for (iter = table.offsetTable.begin(); iter != end; ++iter) { |
| unsigned target = m_currentIndex.offset() + iter->value.branchOffset; |
| if (target == data.fallThrough.bytecodeIndex()) |
| continue; |
| data.cases.append( |
| SwitchCase::withBytecodeIndex(LazyJSValue::knownStringImpl(iter->key.get()), target)); |
| } |
| addToGraph(Switch, OpInfo(&data), get(bytecode.m_scrutinee)); |
| flushIfTerminal(data); |
| LAST_OPCODE(op_switch_string); |
| } |
| |
| case op_ret: { |
| auto bytecode = currentInstruction->as<OpRet>(); |
| ASSERT(!m_currentBlock->terminal()); |
| if (!inlineCallFrame()) { |
| // Simple case: we are just producing a return |
| addToGraph(Return, get(bytecode.m_value)); |
| flushForReturn(); |
| LAST_OPCODE(op_ret); |
| } |
| |
| flushForReturn(); |
| if (m_inlineStackTop->m_returnValue.isValid()) |
| setDirect(m_inlineStackTop->m_returnValue, get(bytecode.m_value), ImmediateSetWithFlush); |
| |
| if (!m_inlineStackTop->m_continuationBlock && m_currentIndex.offset() + currentInstruction->size() != m_inlineStackTop->m_codeBlock->instructions().size()) { |
| // This is an early return from an inlined function and we do not have a continuation block, so we must allocate one. |
| // It is untargetable, because we do not know the appropriate index. |
| // If this block turns out to be a jump target, parseCodeBlock will fix its bytecodeIndex before putting it in m_blockLinkingTargets |
| m_inlineStackTop->m_continuationBlock = allocateUntargetableBlock(); |
| } |
| |
| if (m_inlineStackTop->m_continuationBlock) |
| addJumpTo(m_inlineStackTop->m_continuationBlock); |
| else { |
| // We are returning from an inlined function, and do not need to jump anywhere, so we just keep the current block |
| m_inlineStackTop->m_continuationBlock = m_currentBlock; |
| } |
| LAST_OPCODE_LINKED(op_ret); |
| } |
| case op_end: |
| ASSERT(!inlineCallFrame()); |
| addToGraph(Return, get(currentInstruction->as<OpEnd>().m_value)); |
| flushForReturn(); |
| LAST_OPCODE(op_end); |
| |
| case op_throw: |
| addToGraph(Throw, get(currentInstruction->as<OpThrow>().m_value)); |
| flushForTerminal(); |
| LAST_OPCODE(op_throw); |
| |
| case op_throw_static_error: { |
| auto bytecode = currentInstruction->as<OpThrowStaticError>(); |
| addToGraph(ThrowStaticError, OpInfo(bytecode.m_errorType), get(bytecode.m_message)); |
| flushForTerminal(); |
| LAST_OPCODE(op_throw_static_error); |
| } |
| |
| case op_catch: { |
| auto bytecode = currentInstruction->as<OpCatch>(); |
| m_graph.m_hasExceptionHandlers = true; |
| |
| if (inlineCallFrame()) { |
| // We can't do OSR entry into an inlined frame. |
| NEXT_OPCODE(op_catch); |
| } |
| |
| if (m_graph.m_plan.mode() == FTLForOSREntryMode) { |
| NEXT_OPCODE(op_catch); |
| } |
| |
| RELEASE_ASSERT(!m_currentBlock->size() || (m_graph.compilation() && m_currentBlock->size() == 1 && m_currentBlock->at(0)->op() == CountExecution)); |
| |
| ValueProfileAndVirtualRegisterBuffer* buffer = bytecode.metadata(codeBlock).m_buffer; |
| |
| if (!buffer) { |
| NEXT_OPCODE(op_catch); // This catch has yet to execute. Note: this load can be racy with the main thread. |
| } |
| |
| // We're now committed to compiling this as an entrypoint. |
| m_currentBlock->isCatchEntrypoint = true; |
| m_graph.m_roots.append(m_currentBlock); |
| |
| Vector<SpeculatedType> argumentPredictions(m_numArguments); |
| Vector<SpeculatedType> localPredictions; |
| HashSet<unsigned, WTF::IntHash<unsigned>, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> seenArguments; |
| |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| |
| buffer->forEach([&] (ValueProfileAndVirtualRegister& profile) { |
| VirtualRegister operand(profile.m_operand); |
| SpeculatedType prediction = profile.computeUpdatedPrediction(locker); |
| if (operand.isLocal()) |
| localPredictions.append(prediction); |
| else { |
| RELEASE_ASSERT(operand.isArgument()); |
| RELEASE_ASSERT(static_cast<uint32_t>(operand.toArgument()) < argumentPredictions.size()); |
| if (validationEnabled()) |
| seenArguments.add(operand.toArgument()); |
| argumentPredictions[operand.toArgument()] = prediction; |
| } |
| }); |
| |
| if (validationEnabled()) { |
| for (unsigned argument = 0; argument < m_numArguments; ++argument) |
| RELEASE_ASSERT(seenArguments.contains(argument)); |
| } |
| } |
| |
| Vector<std::pair<VirtualRegister, Node*>> localsToSet; |
| localsToSet.reserveInitialCapacity(buffer->m_size); // Note: This will reserve more than the number of locals we see below because the buffer includes arguments. |
| |
| // We're not allowed to exit here since we would not properly recover values. |
| // We first need to bootstrap the catch entrypoint state. |
| m_exitOK = false; |
| |
| unsigned numberOfLocals = 0; |
| buffer->forEach([&] (ValueProfileAndVirtualRegister& profile) { |
| VirtualRegister operand(profile.m_operand); |
| if (operand.isArgument()) |
| return; |
| ASSERT(operand.isLocal()); |
| Node* value = addToGraph(ExtractCatchLocal, OpInfo(numberOfLocals), OpInfo(localPredictions[numberOfLocals])); |
| ++numberOfLocals; |
| addToGraph(MovHint, OpInfo(operand), value); |
| localsToSet.uncheckedAppend(std::make_pair(operand, value)); |
| }); |
| if (numberOfLocals) |
| addToGraph(ClearCatchLocals); |
| |
| if (!m_graph.m_maxLocalsForCatchOSREntry) |
| m_graph.m_maxLocalsForCatchOSREntry = 0; |
| m_graph.m_maxLocalsForCatchOSREntry = std::max(numberOfLocals, *m_graph.m_maxLocalsForCatchOSREntry); |
| |
| // We could not exit before this point in the program because we would not know how to do value |
| // recovery for live locals. The above IR sets up the necessary state so we can recover values |
| // during OSR exit. |
| // |
| // The nodes that follow here all exit to the following bytecode instruction, not |
| // the op_catch. Exiting to op_catch is reserved for when an exception is thrown. |
| // The SetArgumentDefinitely nodes that follow below may exit because we may hoist type checks |
| // to them. The SetLocal nodes that follow below may exit because we may choose |
| // a flush format that speculates on the type of the local. |
| m_exitOK = true; |
| addToGraph(ExitOK); |
| |
| { |
| auto addResult = m_graph.m_rootToArguments.add(m_currentBlock, ArgumentsVector()); |
| RELEASE_ASSERT(addResult.isNewEntry); |
| ArgumentsVector& entrypointArguments = addResult.iterator->value; |
| entrypointArguments.resize(m_numArguments); |
| |
| BytecodeIndex exitBytecodeIndex = BytecodeIndex(m_currentIndex.offset() + currentInstruction->size()); |
| |
| for (unsigned argument = 0; argument < argumentPredictions.size(); ++argument) { |
| VariableAccessData* variable = newVariableAccessData(virtualRegisterForArgument(argument)); |
| variable->predict(argumentPredictions[argument]); |
| |
| variable->mergeStructureCheckHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(exitBytecodeIndex, BadCache)); |
| variable->mergeCheckArrayHoistingFailed( |
| m_inlineStackTop->m_exitProfile.hasExitSite(exitBytecodeIndex, BadIndexingType)); |
| |
| Node* setArgument = addToGraph(SetArgumentDefinitely, OpInfo(variable)); |
| setArgument->origin.forExit = CodeOrigin(exitBytecodeIndex, setArgument->origin.forExit.inlineCallFrame()); |
| m_currentBlock->variablesAtTail.setArgumentFirstTime(argument, setArgument); |
| entrypointArguments[argument] = setArgument; |
| } |
| } |
| |
| for (const std::pair<VirtualRegister, Node*>& pair : localsToSet) { |
| DelayedSetLocal delayed { currentCodeOrigin(), pair.first, pair.second, ImmediateNakedSet }; |
| m_setLocalQueue.append(delayed); |
| } |
| |
| NEXT_OPCODE(op_catch); |
| } |
| |
| case op_call: |
| handleCall<OpCall>(currentInstruction, Call, CallMode::Regular); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleCall, which may have inlined the callee, trashed m_currentInstruction"); |
| NEXT_OPCODE(op_call); |
| |
| case op_tail_call: { |
| flushForReturn(); |
| Terminality terminality = handleCall<OpTailCall>(currentInstruction, TailCall, CallMode::Tail); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleCall, which may have inlined the callee, trashed m_currentInstruction"); |
| // If the call is terminal then we should not parse any further bytecodes as the TailCall will exit the function. |
| // If the call is not terminal, however, then we want the subsequent op_ret/op_jmp to update metadata and clean |
| // things up. |
| if (terminality == NonTerminal) |
| NEXT_OPCODE(op_tail_call); |
| else |
| LAST_OPCODE_LINKED(op_tail_call); |
| // We use LAST_OPCODE_LINKED instead of LAST_OPCODE because if the tail call was optimized, it may now be a jump to a bytecode index in a different InlineStackEntry. |
| } |
| |
| case op_construct: |
| handleCall<OpConstruct>(currentInstruction, Construct, CallMode::Construct); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleCall, which may have inlined the callee, trashed m_currentInstruction"); |
| NEXT_OPCODE(op_construct); |
| |
| case op_call_varargs: { |
| handleVarargsCall<OpCallVarargs>(currentInstruction, CallVarargs, CallMode::Regular); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleVarargsCall, which may have inlined the callee, trashed m_currentInstruction"); |
| NEXT_OPCODE(op_call_varargs); |
| } |
| |
| case op_tail_call_varargs: { |
| flushForReturn(); |
| Terminality terminality = handleVarargsCall<OpTailCallVarargs>(currentInstruction, TailCallVarargs, CallMode::Tail); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleVarargsCall, which may have inlined the callee, trashed m_currentInstruction"); |
| // If the call is terminal then we should not parse any further bytecodes as the TailCall will exit the function. |
| // If the call is not terminal, however, then we want the subsequent op_ret/op_jmp to update metadata and clean |
| // things up. |
| if (terminality == NonTerminal) |
| NEXT_OPCODE(op_tail_call_varargs); |
| else |
| LAST_OPCODE(op_tail_call_varargs); |
| } |
| |
| case op_tail_call_forward_arguments: { |
| // We need to make sure that we don't unbox our arguments here since that won't be |
| // done by the arguments object creation node as that node may not exist. |
| noticeArgumentsUse(); |
| flushForReturn(); |
| Terminality terminality = handleVarargsCall<OpTailCallForwardArguments>(currentInstruction, TailCallForwardVarargs, CallMode::Tail); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleVarargsCall, which may have inlined the callee, trashed m_currentInstruction"); |
| // If the call is terminal then we should not parse any further bytecodes as the TailCall will exit the function. |
| // If the call is not terminal, however, then we want the subsequent op_ret/op_jmp to update metadata and clean |
| // things up. |
| if (terminality == NonTerminal) |
| NEXT_OPCODE(op_tail_call_forward_arguments); |
| else |
| LAST_OPCODE(op_tail_call_forward_arguments); |
| } |
| |
| case op_construct_varargs: { |
| handleVarargsCall<OpConstructVarargs>(currentInstruction, ConstructVarargs, CallMode::Construct); |
| ASSERT_WITH_MESSAGE(m_currentInstruction == currentInstruction, "handleVarargsCall, which may have inlined the callee, trashed m_currentInstruction"); |
| NEXT_OPCODE(op_construct_varargs); |
| } |
| |
| case op_call_eval: { |
| auto bytecode = currentInstruction->as<OpCallEval>(); |
| int registerOffset = -bytecode.m_argv; |
| addCall(bytecode.m_dst, CallEval, nullptr, get(bytecode.m_callee), bytecode.m_argc, registerOffset, getPrediction()); |
| NEXT_OPCODE(op_call_eval); |
| } |
| |
| case op_jneq_ptr: { |
| auto bytecode = currentInstruction->as<OpJneqPtr>(); |
| FrozenValue* frozenPointer = m_graph.freezeStrong(m_inlineStackTop->m_codeBlock->getConstant(bytecode.m_specialPointer)); |
| unsigned relativeOffset = jumpTarget(bytecode.m_targetLabel); |
| Node* child = get(bytecode.m_value); |
| if (bytecode.metadata(codeBlock).m_hasJumped) { |
| Node* condition = addToGraph(CompareEqPtr, OpInfo(frozenPointer), child); |
| addToGraph(Branch, OpInfo(branchData(m_currentIndex.offset() + currentInstruction->size(), m_currentIndex.offset() + relativeOffset)), condition); |
| LAST_OPCODE(op_jneq_ptr); |
| } |
| addToGraph(CheckCell, OpInfo(frozenPointer), child); |
| NEXT_OPCODE(op_jneq_ptr); |
| } |
| |
| case op_resolve_scope: { |
| auto bytecode = currentInstruction->as<OpResolveScope>(); |
| auto& metadata = bytecode.metadata(codeBlock); |
| |
| ResolveType resolveType; |
| unsigned depth; |
| JSScope* constantScope = nullptr; |
| JSCell* lexicalEnvironment = nullptr; |
| SymbolTable* symbolTable = nullptr; |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| resolveType = metadata.m_resolveType; |
| depth = metadata.m_localScopeDepth; |
| switch (resolveType) { |
| case GlobalProperty: |
| case GlobalVar: |
| case GlobalPropertyWithVarInjectionChecks: |
| case GlobalVarWithVarInjectionChecks: |
| case GlobalLexicalVar: |
| case GlobalLexicalVarWithVarInjectionChecks: |
| constantScope = metadata.m_constantScope.get(); |
| break; |
| case ModuleVar: |
| lexicalEnvironment = metadata.m_lexicalEnvironment.get(); |
| break; |
| case LocalClosureVar: |
| case ClosureVar: |
| case ClosureVarWithVarInjectionChecks: |
| symbolTable = metadata.m_symbolTable.get(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (needsDynamicLookup(resolveType, op_resolve_scope)) { |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_var]; |
| set(bytecode.m_dst, addToGraph(ResolveScope, OpInfo(identifierNumber), get(bytecode.m_scope))); |
| NEXT_OPCODE(op_resolve_scope); |
| } |
| |
| // get_from_scope and put_to_scope depend on this watchpoint forcing OSR exit, so they don't add their own watchpoints. |
| if (needsVarInjectionChecks(resolveType)) |
| m_graph.watchpoints().addLazily(m_inlineStackTop->m_codeBlock->globalObject()->varInjectionWatchpoint()); |
| |
| // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed. |
| // https://bugs.webkit.org/show_bug.cgi?id=193347 |
| if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) { |
| if (resolveType == GlobalProperty || resolveType == GlobalPropertyWithVarInjectionChecks) { |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_var]; |
| if (!m_graph.watchGlobalProperty(globalObject, identifierNumber)) |
| addToGraph(ForceOSRExit); |
| } |
| } |
| |
| switch (resolveType) { |
| case GlobalProperty: |
| case GlobalVar: |
| case GlobalPropertyWithVarInjectionChecks: |
| case GlobalVarWithVarInjectionChecks: |
| case GlobalLexicalVar: |
| case GlobalLexicalVarWithVarInjectionChecks: { |
| RELEASE_ASSERT(constantScope); |
| RELEASE_ASSERT(constantScope == JSScope::constantScopeForCodeBlock(resolveType, m_inlineStackTop->m_codeBlock)); |
| set(bytecode.m_dst, weakJSConstant(constantScope)); |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| break; |
| } |
| case ModuleVar: { |
| // Since the value of the "scope" virtual register is not used in LLInt / baseline op_resolve_scope with ModuleVar, |
| // we need not to keep it alive by the Phantom node. |
| // Module environment is already strongly referenced by the CodeBlock. |
| set(bytecode.m_dst, weakJSConstant(lexicalEnvironment)); |
| break; |
| } |
| case LocalClosureVar: |
| case ClosureVar: |
| case ClosureVarWithVarInjectionChecks: { |
| Node* localBase = get(bytecode.m_scope); |
| addToGraph(Phantom, localBase); // OSR exit cannot handle resolve_scope on a DCE'd scope. |
| |
| // We have various forms of constant folding here. This is necessary to avoid |
| // spurious recompiles in dead-but-foldable code. |
| |
| if (symbolTable) { |
| if (JSScope* scope = symbolTable->singleton().inferredValue()) { |
| m_graph.watchpoints().addLazily(symbolTable); |
| set(bytecode.m_dst, weakJSConstant(scope)); |
| break; |
| } |
| } |
| if (JSScope* scope = localBase->dynamicCastConstant<JSScope*>(*m_vm)) { |
| for (unsigned n = depth; n--;) |
| scope = scope->next(); |
| set(bytecode.m_dst, weakJSConstant(scope)); |
| break; |
| } |
| for (unsigned n = depth; n--;) |
| localBase = addToGraph(SkipScope, localBase); |
| set(bytecode.m_dst, localBase); |
| break; |
| } |
| case UnresolvedProperty: |
| case UnresolvedPropertyWithVarInjectionChecks: { |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| addToGraph(ForceOSRExit); |
| set(bytecode.m_dst, addToGraph(JSConstant, OpInfo(m_constantNull))); |
| break; |
| } |
| case Dynamic: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| NEXT_OPCODE(op_resolve_scope); |
| } |
| case op_resolve_scope_for_hoisting_func_decl_in_eval: { |
| auto bytecode = currentInstruction->as<OpResolveScopeForHoistingFuncDeclInEval>(); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| set(bytecode.m_dst, addToGraph(ResolveScopeForHoistingFuncDeclInEval, OpInfo(identifierNumber), get(bytecode.m_scope))); |
| |
| NEXT_OPCODE(op_resolve_scope_for_hoisting_func_decl_in_eval); |
| } |
| |
| case op_get_from_scope: { |
| auto bytecode = currentInstruction->as<OpGetFromScope>(); |
| auto& metadata = bytecode.metadata(codeBlock); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_var]; |
| UniquedStringImpl* uid = m_graph.identifiers()[identifierNumber]; |
| |
| ResolveType resolveType; |
| GetPutInfo getPutInfo(0); |
| Structure* structure = 0; |
| WatchpointSet* watchpoints = 0; |
| uintptr_t operand; |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| getPutInfo = metadata.m_getPutInfo; |
| resolveType = getPutInfo.resolveType(); |
| if (resolveType == GlobalVar || resolveType == GlobalVarWithVarInjectionChecks || resolveType == GlobalLexicalVar || resolveType == GlobalLexicalVarWithVarInjectionChecks) |
| watchpoints = metadata.m_watchpointSet; |
| else if (resolveType != UnresolvedProperty && resolveType != UnresolvedPropertyWithVarInjectionChecks) |
| structure = metadata.m_structure.get(); |
| operand = metadata.m_operand; |
| } |
| |
| if (needsDynamicLookup(resolveType, op_get_from_scope)) { |
| uint64_t opInfo1 = makeDynamicVarOpInfo(identifierNumber, getPutInfo.operand()); |
| SpeculatedType prediction = getPrediction(); |
| set(bytecode.m_dst, |
| addToGraph(GetDynamicVar, OpInfo(opInfo1), OpInfo(prediction), get(bytecode.m_scope))); |
| NEXT_OPCODE(op_get_from_scope); |
| } |
| |
| UNUSED_PARAM(watchpoints); // We will use this in the future. For now we set it as a way of documenting the fact that that's what index 5 is in GlobalVar mode. |
| |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| |
| switch (resolveType) { |
| case GlobalProperty: |
| case GlobalPropertyWithVarInjectionChecks: { |
| // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed. |
| // https://bugs.webkit.org/show_bug.cgi?id=193347 |
| if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) { |
| if (!m_graph.watchGlobalProperty(globalObject, identifierNumber)) |
| addToGraph(ForceOSRExit); |
| } |
| |
| SpeculatedType prediction = getPrediction(); |
| |
| GetByStatus status = GetByStatus::computeFor(structure, uid); |
| if (status.state() != GetByStatus::Simple |
| || status.numVariants() != 1 |
| || status[0].structureSet().size() != 1) { |
| set(bytecode.m_dst, addToGraph(GetByIdFlush, OpInfo(identifierNumber), OpInfo(prediction), get(bytecode.m_scope))); |
| break; |
| } |
| |
| Node* base = weakJSConstant(globalObject); |
| Node* result = load(prediction, base, identifierNumber, status[0]); |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| set(bytecode.m_dst, result); |
| break; |
| } |
| case GlobalVar: |
| case GlobalVarWithVarInjectionChecks: |
| case GlobalLexicalVar: |
| case GlobalLexicalVarWithVarInjectionChecks: { |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| WatchpointSet* watchpointSet; |
| ScopeOffset offset; |
| JSSegmentedVariableObject* scopeObject = jsCast<JSSegmentedVariableObject*>(JSScope::constantScopeForCodeBlock(resolveType, m_inlineStackTop->m_codeBlock)); |
| { |
| ConcurrentJSLocker locker(scopeObject->symbolTable()->m_lock); |
| SymbolTableEntry entry = scopeObject->symbolTable()->get(locker, uid); |
| watchpointSet = entry.watchpointSet(); |
| offset = entry.scopeOffset(); |
| } |
| if (watchpointSet && watchpointSet->state() == IsWatched) { |
| // This has a fun concurrency story. There is the possibility of a race in two |
| // directions: |
| // |
| // We see that the set IsWatched, but in the meantime it gets invalidated: this is |
| // fine because if we saw that it IsWatched then we add a watchpoint. If it gets |
| // invalidated, then this compilation is invalidated. Note that in the meantime we |
| // may load an absurd value from the global object. It's fine to load an absurd |
| // value if the compilation is invalidated anyway. |
| // |
| // We see that the set IsWatched, but the value isn't yet initialized: this isn't |
| // possible because of the ordering of operations. |
| // |
| // Here's how we order operations: |
| // |
| // Main thread stores to the global object: always store a value first, and only |
| // after that do we touch the watchpoint set. There is a fence in the touch, that |
| // ensures that the store to the global object always happens before the touch on the |
| // set. |
| // |
| // Compilation thread: always first load the state of the watchpoint set, and then |
| // load the value. The WatchpointSet::state() method does fences for us to ensure |
| // that the load of the state happens before our load of the value. |
| // |
| // Finalizing compilation: this happens on the main thread and synchronously checks |
| // validity of all watchpoint sets. |
| // |
| // We will only perform optimizations if the load of the state yields IsWatched. That |
| // means that at least one store would have happened to initialize the original value |
| // of the variable (that is, the value we'd like to constant fold to). There may be |
| // other stores that happen after that, but those stores will invalidate the |
| // watchpoint set and also the compilation. |
| |
| // Note that we need to use the operand, which is a direct pointer at the global, |
| // rather than looking up the global by doing variableAt(offset). That's because the |
| // internal data structures of JSSegmentedVariableObject are not thread-safe even |
| // though accessing the global itself is. The segmentation involves a vector spine |
| // that resizes with malloc/free, so if new globals unrelated to the one we are |
| // reading are added, we might access freed memory if we do variableAt(). |
| WriteBarrier<Unknown>* pointer = bitwise_cast<WriteBarrier<Unknown>*>(operand); |
| |
| ASSERT(scopeObject->findVariableIndex(pointer) == offset); |
| |
| JSValue value = pointer->get(); |
| if (value) { |
| m_graph.watchpoints().addLazily(watchpointSet); |
| set(bytecode.m_dst, weakJSConstant(value)); |
| break; |
| } |
| } |
| |
| SpeculatedType prediction = getPrediction(); |
| NodeType nodeType; |
| if (resolveType == GlobalVar || resolveType == GlobalVarWithVarInjectionChecks) |
| nodeType = GetGlobalVar; |
| else |
| nodeType = GetGlobalLexicalVariable; |
| Node* value = addToGraph(nodeType, OpInfo(operand), OpInfo(prediction)); |
| if (resolveType == GlobalLexicalVar || resolveType == GlobalLexicalVarWithVarInjectionChecks) |
| addToGraph(CheckNotEmpty, value); |
| set(bytecode.m_dst, value); |
| break; |
| } |
| case LocalClosureVar: |
| case ClosureVar: |
| case ClosureVarWithVarInjectionChecks: { |
| Node* scopeNode = get(bytecode.m_scope); |
| |
| // Ideally we wouldn't have to do this Phantom. But: |
| // |
| // For the constant case: we must do it because otherwise we would have no way of knowing |
| // that the scope is live at OSR here. |
| // |
| // For the non-constant case: GetClosureVar could be DCE'd, but baseline's implementation |
| // won't be able to handle an Undefined scope. |
| addToGraph(Phantom, scopeNode); |
| |
| // Constant folding in the bytecode parser is important for performance. This may not |
| // have executed yet. If it hasn't, then we won't have a prediction. Lacking a |
| // prediction, we'd otherwise think that it has to exit. Then when it did execute, we |
| // would recompile. But if we can fold it here, we avoid the exit. |
| if (JSValue value = m_graph.tryGetConstantClosureVar(scopeNode, ScopeOffset(operand))) { |
| set(bytecode.m_dst, weakJSConstant(value)); |
| break; |
| } |
| SpeculatedType prediction = getPrediction(); |
| set(bytecode.m_dst, |
| addToGraph(GetClosureVar, OpInfo(operand), OpInfo(prediction), scopeNode)); |
| break; |
| } |
| case UnresolvedProperty: |
| case UnresolvedPropertyWithVarInjectionChecks: |
| case ModuleVar: |
| case Dynamic: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| NEXT_OPCODE(op_get_from_scope); |
| } |
| |
| case op_put_to_scope: { |
| auto bytecode = currentInstruction->as<OpPutToScope>(); |
| auto& metadata = bytecode.metadata(codeBlock); |
| unsigned identifierNumber = bytecode.m_var; |
| if (identifierNumber != UINT_MAX) |
| identifierNumber = m_inlineStackTop->m_identifierRemap[identifierNumber]; |
| UniquedStringImpl* uid; |
| if (identifierNumber != UINT_MAX) |
| uid = m_graph.identifiers()[identifierNumber]; |
| else |
| uid = nullptr; |
| |
| ResolveType resolveType; |
| GetPutInfo getPutInfo(0); |
| Structure* structure = nullptr; |
| WatchpointSet* watchpoints = nullptr; |
| uintptr_t operand; |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| getPutInfo = metadata.m_getPutInfo; |
| resolveType = getPutInfo.resolveType(); |
| if (resolveType == GlobalVar || resolveType == GlobalVarWithVarInjectionChecks || resolveType == LocalClosureVar || resolveType == GlobalLexicalVar || resolveType == GlobalLexicalVarWithVarInjectionChecks) |
| watchpoints = metadata.m_watchpointSet; |
| else if (resolveType != UnresolvedProperty && resolveType != UnresolvedPropertyWithVarInjectionChecks) |
| structure = metadata.m_structure.get(); |
| operand = metadata.m_operand; |
| } |
| |
| JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject(); |
| |
| if (needsDynamicLookup(resolveType, op_put_to_scope)) { |
| ASSERT(identifierNumber != UINT_MAX); |
| uint64_t opInfo1 = makeDynamicVarOpInfo(identifierNumber, getPutInfo.operand()); |
| addToGraph(PutDynamicVar, OpInfo(opInfo1), OpInfo(), get(bytecode.m_scope), get(bytecode.m_value)); |
| NEXT_OPCODE(op_put_to_scope); |
| } |
| |
| switch (resolveType) { |
| case GlobalProperty: |
| case GlobalPropertyWithVarInjectionChecks: { |
| // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed. |
| // https://bugs.webkit.org/show_bug.cgi?id=193347 |
| if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) { |
| if (!m_graph.watchGlobalProperty(globalObject, identifierNumber)) |
| addToGraph(ForceOSRExit); |
| } |
| |
| PutByIdStatus status; |
| if (uid) |
| status = PutByIdStatus::computeFor(globalObject, structure, uid, false); |
| else |
| status = PutByIdStatus(PutByIdStatus::TakesSlowPath); |
| if (status.numVariants() != 1 |
| || status[0].kind() != PutByIdVariant::Replace |
| || status[0].structure().size() != 1) { |
| addToGraph(PutById, OpInfo(identifierNumber), get(bytecode.m_scope), get(bytecode.m_value)); |
| break; |
| } |
| Node* base = weakJSConstant(globalObject); |
| store(base, identifierNumber, status[0], get(bytecode.m_value)); |
| // Keep scope alive until after put. |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| break; |
| } |
| case GlobalLexicalVar: |
| case GlobalLexicalVarWithVarInjectionChecks: |
| case GlobalVar: |
| case GlobalVarWithVarInjectionChecks: { |
| if (!isInitialization(getPutInfo.initializationMode()) && (resolveType == GlobalLexicalVar || resolveType == GlobalLexicalVarWithVarInjectionChecks)) { |
| SpeculatedType prediction = SpecEmpty; |
| Node* value = addToGraph(GetGlobalLexicalVariable, OpInfo(operand), OpInfo(prediction)); |
| addToGraph(CheckNotEmpty, value); |
| } |
| |
| JSSegmentedVariableObject* scopeObject = jsCast<JSSegmentedVariableObject*>(JSScope::constantScopeForCodeBlock(resolveType, m_inlineStackTop->m_codeBlock)); |
| if (watchpoints) { |
| SymbolTableEntry entry = scopeObject->symbolTable()->get(uid); |
| ASSERT_UNUSED(entry, watchpoints == entry.watchpointSet()); |
| } |
| Node* valueNode = get(bytecode.m_value); |
| addToGraph(PutGlobalVariable, OpInfo(operand), weakJSConstant(scopeObject), valueNode); |
| if (watchpoints && watchpoints->state() != IsInvalidated) { |
| // Must happen after the store. See comment for GetGlobalVar. |
| addToGraph(NotifyWrite, OpInfo(watchpoints)); |
| } |
| // Keep scope alive until after put. |
| addToGraph(Phantom, get(bytecode.m_scope)); |
| break; |
| } |
| case LocalClosureVar: |
| case ClosureVar: |
| case ClosureVarWithVarInjectionChecks: { |
| Node* scopeNode = get(bytecode.m_scope); |
| Node* valueNode = get(bytecode.m_value); |
| |
| addToGraph(PutClosureVar, OpInfo(operand), scopeNode, valueNode); |
| |
| if (watchpoints && watchpoints->state() != IsInvalidated) { |
| // Must happen after the store. See comment for GetGlobalVar. |
| addToGraph(NotifyWrite, OpInfo(watchpoints)); |
| } |
| break; |
| } |
| |
| case ModuleVar: |
| // Need not to keep "scope" and "value" register values here by Phantom because |
| // they are not used in LLInt / baseline op_put_to_scope with ModuleVar. |
| addToGraph(ForceOSRExit); |
| break; |
| |
| case Dynamic: |
| case UnresolvedProperty: |
| case UnresolvedPropertyWithVarInjectionChecks: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| NEXT_OPCODE(op_put_to_scope); |
| } |
| |
| case op_loop_hint: { |
| // Baseline->DFG OSR jumps between loop hints. The DFG assumes that Baseline->DFG |
| // OSR can only happen at basic block boundaries. Assert that these two statements |
| // are compatible. |
| RELEASE_ASSERT(m_currentIndex == blockBegin); |
| |
| // We never do OSR into an inlined code block. That could not happen, since OSR |
| // looks up the code block that is the replacement for the baseline JIT code |
| // block. Hence, machine code block = true code block = not inline code block. |
| if (!m_inlineStackTop->m_caller) |
| m_currentBlock->isOSRTarget = true; |
| |
| addToGraph(LoopHint); |
| NEXT_OPCODE(op_loop_hint); |
| } |
| |
| case op_check_traps: { |
| addToGraph(Options::usePollingTraps() ? CheckTraps : InvalidationPoint); |
| NEXT_OPCODE(op_check_traps); |
| } |
| |
| case op_nop: { |
| addToGraph(Check); // We add a nop here so that basic block linking doesn't break. |
| NEXT_OPCODE(op_nop); |
| } |
| |
| case op_super_sampler_begin: { |
| addToGraph(SuperSamplerBegin); |
| NEXT_OPCODE(op_super_sampler_begin); |
| } |
| |
| case op_super_sampler_end: { |
| addToGraph(SuperSamplerEnd); |
| NEXT_OPCODE(op_super_sampler_end); |
| } |
| |
| case op_create_lexical_environment: { |
| auto bytecode = currentInstruction->as<OpCreateLexicalEnvironment>(); |
| ASSERT(bytecode.m_symbolTable.isConstant() && bytecode.m_initialValue.isConstant()); |
| FrozenValue* symbolTable = m_graph.freezeStrong(m_inlineStackTop->m_codeBlock->getConstant(bytecode.m_symbolTable)); |
| FrozenValue* initialValue = m_graph.freezeStrong(m_inlineStackTop->m_codeBlock->getConstant(bytecode.m_initialValue)); |
| Node* scope = get(bytecode.m_scope); |
| Node* lexicalEnvironment = addToGraph(CreateActivation, OpInfo(symbolTable), OpInfo(initialValue), scope); |
| set(bytecode.m_dst, lexicalEnvironment); |
| NEXT_OPCODE(op_create_lexical_environment); |
| } |
| |
| case op_push_with_scope: { |
| auto bytecode = currentInstruction->as<OpPushWithScope>(); |
| Node* currentScope = get(bytecode.m_currentScope); |
| Node* object = get(bytecode.m_newScope); |
| set(bytecode.m_dst, addToGraph(PushWithScope, currentScope, object)); |
| NEXT_OPCODE(op_push_with_scope); |
| } |
| |
| case op_get_parent_scope: { |
| auto bytecode = currentInstruction->as<OpGetParentScope>(); |
| Node* currentScope = get(bytecode.m_scope); |
| Node* newScope = addToGraph(SkipScope, currentScope); |
| set(bytecode.m_dst, newScope); |
| addToGraph(Phantom, currentScope); |
| NEXT_OPCODE(op_get_parent_scope); |
| } |
| |
| case op_get_scope: { |
| // Help the later stages a bit by doing some small constant folding here. Note that this |
| // only helps for the first basic block. It's extremely important not to constant fold |
| // loads from the scope register later, as that would prevent the DFG from tracking the |
| // bytecode-level liveness of the scope register. |
| auto bytecode = currentInstruction->as<OpGetScope>(); |
| Node* callee = get(CallFrameSlot::callee); |
| Node* result; |
| if (JSFunction* function = callee->dynamicCastConstant<JSFunction*>(*m_vm)) |
| result = weakJSConstant(function->scope()); |
| else |
| result = addToGraph(GetScope, callee); |
| set(bytecode.m_dst, result); |
| NEXT_OPCODE(op_get_scope); |
| } |
| |
| case op_argument_count: { |
| auto bytecode = currentInstruction->as<OpArgumentCount>(); |
| Node* sub = addToGraph(ArithSub, OpInfo(Arith::Unchecked), OpInfo(SpecInt32Only), getArgumentCount(), addToGraph(JSConstant, OpInfo(m_constantOne))); |
| set(bytecode.m_dst, sub); |
| NEXT_OPCODE(op_argument_count); |
| } |
| |
| case op_create_direct_arguments: { |
| auto bytecode = currentInstruction->as<OpCreateDirectArguments>(); |
| noticeArgumentsUse(); |
| Node* createArguments = addToGraph(CreateDirectArguments); |
| set(bytecode.m_dst, createArguments); |
| NEXT_OPCODE(op_create_direct_arguments); |
| } |
| |
| case op_create_scoped_arguments: { |
| auto bytecode = currentInstruction->as<OpCreateScopedArguments>(); |
| noticeArgumentsUse(); |
| Node* createArguments = addToGraph(CreateScopedArguments, get(bytecode.m_scope)); |
| set(bytecode.m_dst, createArguments); |
| NEXT_OPCODE(op_create_scoped_arguments); |
| } |
| |
| case op_create_cloned_arguments: { |
| auto bytecode = currentInstruction->as<OpCreateClonedArguments>(); |
| noticeArgumentsUse(); |
| Node* createArguments = addToGraph(CreateClonedArguments); |
| set(bytecode.m_dst, createArguments); |
| NEXT_OPCODE(op_create_cloned_arguments); |
| } |
| |
| case op_create_arguments_butterfly: { |
| auto bytecode = currentInstruction->as<OpCreateArgumentsButterfly>(); |
| noticeArgumentsUse(); |
| set(bytecode.m_dst, addToGraph(CreateArgumentsButterfly)); |
| NEXT_OPCODE(op_create_arguments_butterfly); |
| } |
| |
| case op_get_from_arguments: { |
| auto bytecode = currentInstruction->as<OpGetFromArguments>(); |
| set(bytecode.m_dst, |
| addToGraph( |
| GetFromArguments, |
| OpInfo(bytecode.m_index), |
| OpInfo(getPrediction()), |
| get(bytecode.m_arguments))); |
| NEXT_OPCODE(op_get_from_arguments); |
| } |
| |
| case op_put_to_arguments: { |
| auto bytecode = currentInstruction->as<OpPutToArguments>(); |
| addToGraph( |
| PutToArguments, |
| OpInfo(bytecode.m_index), |
| get(bytecode.m_arguments), |
| get(bytecode.m_value)); |
| NEXT_OPCODE(op_put_to_arguments); |
| } |
| |
| case op_get_argument: { |
| auto bytecode = currentInstruction->as<OpGetArgument>(); |
| InlineCallFrame* inlineCallFrame = this->inlineCallFrame(); |
| Node* argument; |
| int32_t argumentIndexIncludingThis = bytecode.m_index; |
| if (inlineCallFrame && !inlineCallFrame->isVarargs()) { |
| int32_t argumentCountIncludingThisWithFixup = inlineCallFrame->argumentsWithFixup.size(); |
| if (argumentIndexIncludingThis < argumentCountIncludingThisWithFixup) |
| argument = get(virtualRegisterForArgument(argumentIndexIncludingThis)); |
| else |
| argument = addToGraph(JSConstant, OpInfo(m_constantUndefined)); |
| } else |
| argument = addToGraph(GetArgument, OpInfo(argumentIndexIncludingThis), OpInfo(getPrediction())); |
| set(bytecode.m_dst, argument); |
| NEXT_OPCODE(op_get_argument); |
| } |
| case op_new_async_generator_func: |
| handleNewFunc(NewAsyncGeneratorFunction, currentInstruction->as<OpNewAsyncGeneratorFunc>()); |
| NEXT_OPCODE(op_new_async_generator_func); |
| case op_new_func: |
| handleNewFunc(NewFunction, currentInstruction->as<OpNewFunc>()); |
| NEXT_OPCODE(op_new_func); |
| case op_new_generator_func: |
| handleNewFunc(NewGeneratorFunction, currentInstruction->as<OpNewGeneratorFunc>()); |
| NEXT_OPCODE(op_new_generator_func); |
| case op_new_async_func: |
| handleNewFunc(NewAsyncFunction, currentInstruction->as<OpNewAsyncFunc>()); |
| NEXT_OPCODE(op_new_async_func); |
| |
| case op_new_func_exp: |
| handleNewFuncExp(NewFunction, currentInstruction->as<OpNewFuncExp>()); |
| NEXT_OPCODE(op_new_func_exp); |
| case op_new_generator_func_exp: |
| handleNewFuncExp(NewGeneratorFunction, currentInstruction->as<OpNewGeneratorFuncExp>()); |
| NEXT_OPCODE(op_new_generator_func_exp); |
| case op_new_async_generator_func_exp: |
| handleNewFuncExp(NewAsyncGeneratorFunction, currentInstruction->as<OpNewAsyncGeneratorFuncExp>()); |
| NEXT_OPCODE(op_new_async_generator_func_exp); |
| case op_new_async_func_exp: |
| handleNewFuncExp(NewAsyncFunction, currentInstruction->as<OpNewAsyncFuncExp>()); |
| NEXT_OPCODE(op_new_async_func_exp); |
| |
| case op_set_function_name: { |
| auto bytecode = currentInstruction->as<OpSetFunctionName>(); |
| Node* func = get(bytecode.m_function); |
| Node* name = get(bytecode.m_name); |
| addToGraph(SetFunctionName, func, name); |
| NEXT_OPCODE(op_set_function_name); |
| } |
| |
| case op_typeof: { |
| auto bytecode = currentInstruction->as<OpTypeof>(); |
| set(bytecode.m_dst, addToGraph(TypeOf, get(bytecode.m_value))); |
| NEXT_OPCODE(op_typeof); |
| } |
| |
| case op_to_number: { |
| auto bytecode = currentInstruction->as<OpToNumber>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(ToNumber, OpInfo(0), OpInfo(prediction), value)); |
| NEXT_OPCODE(op_to_number); |
| } |
| |
| case op_to_numeric: { |
| auto bytecode = currentInstruction->as<OpToNumeric>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(ToNumeric, OpInfo(0), OpInfo(prediction), value)); |
| NEXT_OPCODE(op_to_numeric); |
| } |
| |
| case op_to_string: { |
| auto bytecode = currentInstruction->as<OpToString>(); |
| Node* value = get(bytecode.m_operand); |
| set(bytecode.m_dst, addToGraph(ToString, value)); |
| NEXT_OPCODE(op_to_string); |
| } |
| |
| case op_to_object: { |
| auto bytecode = currentInstruction->as<OpToObject>(); |
| SpeculatedType prediction = getPrediction(); |
| Node* value = get(bytecode.m_operand); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_message]; |
| set(bytecode.m_dst, addToGraph(ToObject, OpInfo(identifierNumber), OpInfo(prediction), value)); |
| NEXT_OPCODE(op_to_object); |
| } |
| |
| case op_in_by_val: { |
| auto bytecode = currentInstruction->as<OpInByVal>(); |
| ArrayMode arrayMode = getArrayMode(bytecode.metadata(codeBlock).m_arrayProfile, Array::Read); |
| set(bytecode.m_dst, addToGraph(InByVal, OpInfo(arrayMode.asWord()), get(bytecode.m_base), get(bytecode.m_property))); |
| NEXT_OPCODE(op_in_by_val); |
| } |
| |
| case op_in_by_id: { |
| auto bytecode = currentInstruction->as<OpInById>(); |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| UniquedStringImpl* uid = m_graph.identifiers()[identifierNumber]; |
| |
| InByIdStatus status = InByIdStatus::computeFor( |
| m_inlineStackTop->m_profiledBlock, |
| m_inlineStackTop->m_baselineMap, m_icContextStack, |
| currentCodeOrigin(), uid); |
| |
| if (status.isSimple()) { |
| bool allOK = true; |
| MatchStructureData* data = m_graph.m_matchStructureData.add(); |
| for (const InByIdVariant& variant : status.variants()) { |
| if (!check(variant.conditionSet())) { |
| allOK = false; |
| break; |
| } |
| for (Structure* structure : variant.structureSet()) { |
| MatchStructureVariant matchVariant; |
| matchVariant.structure = m_graph.registerStructure(structure); |
| matchVariant.result = variant.isHit(); |
| |
| data->variants.append(WTFMove(matchVariant)); |
| } |
| } |
| |
| if (allOK) { |
| addToGraph(FilterInByIdStatus, OpInfo(m_graph.m_plan.recordedStatuses().addInByIdStatus(currentCodeOrigin(), status)), base); |
| |
| Node* match = addToGraph(MatchStructure, OpInfo(data), base); |
| set(bytecode.m_dst, match); |
| NEXT_OPCODE(op_in_by_id); |
| } |
| } |
| |
| set(bytecode.m_dst, addToGraph(InById, OpInfo(identifierNumber), base)); |
| NEXT_OPCODE(op_in_by_id); |
| } |
| |
| case op_get_enumerable_length: { |
| auto bytecode = currentInstruction->as<OpGetEnumerableLength>(); |
| set(bytecode.m_dst, addToGraph(GetEnumerableLength, get(bytecode.m_base))); |
| NEXT_OPCODE(op_get_enumerable_length); |
| } |
| |
| case op_has_generic_property: { |
| auto bytecode = currentInstruction->as<OpHasGenericProperty>(); |
| set(bytecode.m_dst, addToGraph(HasGenericProperty, get(bytecode.m_base), get(bytecode.m_property))); |
| NEXT_OPCODE(op_has_generic_property); |
| } |
| |
| case op_has_structure_property: { |
| auto bytecode = currentInstruction->as<OpHasStructureProperty>(); |
| set(bytecode.m_dst, addToGraph(HasStructureProperty, |
| get(bytecode.m_base), |
| get(bytecode.m_property), |
| get(bytecode.m_enumerator))); |
| NEXT_OPCODE(op_has_structure_property); |
| } |
| |
| case op_has_indexed_property: { |
| auto bytecode = currentInstruction->as<OpHasIndexedProperty>(); |
| Node* base = get(bytecode.m_base); |
| ArrayMode arrayMode = getArrayMode(bytecode.metadata(codeBlock).m_arrayProfile, Array::Read); |
| Node* property = get(bytecode.m_property); |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(nullptr); |
| Node* hasIterableProperty = addToGraph(Node::VarArg, HasIndexedProperty, OpInfo(arrayMode.asWord()), OpInfo(static_cast<uint32_t>(PropertySlot::InternalMethodType::GetOwnProperty))); |
| m_exitOK = false; // HasIndexedProperty must be treated as if it clobbers exit state, since FixupPhase may make it generic. |
| set(bytecode.m_dst, hasIterableProperty); |
| NEXT_OPCODE(op_has_indexed_property); |
| } |
| |
| case op_get_direct_pname: { |
| auto bytecode = currentInstruction->as<OpGetDirectPname>(); |
| SpeculatedType prediction = getPredictionWithoutOSRExit(); |
| |
| Node* base = get(bytecode.m_base); |
| Node* property = get(bytecode.m_property); |
| Node* index = get(bytecode.m_index); |
| Node* enumerator = get(bytecode.m_enumerator); |
| |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(index); |
| addVarArgChild(enumerator); |
| set(bytecode.m_dst, addToGraph(Node::VarArg, GetDirectPname, OpInfo(0), OpInfo(prediction))); |
| |
| NEXT_OPCODE(op_get_direct_pname); |
| } |
| |
| case op_get_property_enumerator: { |
| auto bytecode = currentInstruction->as<OpGetPropertyEnumerator>(); |
| set(bytecode.m_dst, addToGraph(GetPropertyEnumerator, get(bytecode.m_base))); |
| NEXT_OPCODE(op_get_property_enumerator); |
| } |
| |
| case op_enumerator_structure_pname: { |
| auto bytecode = currentInstruction->as<OpEnumeratorStructurePname>(); |
| set(bytecode.m_dst, addToGraph(GetEnumeratorStructurePname, |
| get(bytecode.m_enumerator), |
| get(bytecode.m_index))); |
| NEXT_OPCODE(op_enumerator_structure_pname); |
| } |
| |
| case op_enumerator_generic_pname: { |
| auto bytecode = currentInstruction->as<OpEnumeratorGenericPname>(); |
| set(bytecode.m_dst, addToGraph(GetEnumeratorGenericPname, |
| get(bytecode.m_enumerator), |
| get(bytecode.m_index))); |
| NEXT_OPCODE(op_enumerator_generic_pname); |
| } |
| |
| case op_to_index_string: { |
| auto bytecode = currentInstruction->as<OpToIndexString>(); |
| set(bytecode.m_dst, addToGraph(ToIndexString, get(bytecode.m_index))); |
| NEXT_OPCODE(op_to_index_string); |
| } |
| |
| case op_get_internal_field: { |
| auto bytecode = currentInstruction->as<OpGetInternalField>(); |
| set(bytecode.m_dst, addToGraph(GetInternalField, OpInfo(bytecode.m_index), OpInfo(getPrediction()), get(bytecode.m_base))); |
| NEXT_OPCODE(op_get_internal_field); |
| } |
| |
| case op_put_internal_field: { |
| auto bytecode = currentInstruction->as<OpPutInternalField>(); |
| addToGraph(PutInternalField, OpInfo(bytecode.m_index), get(bytecode.m_base), get(bytecode.m_value)); |
| NEXT_OPCODE(op_put_internal_field); |
| } |
| |
| case op_log_shadow_chicken_prologue: { |
| auto bytecode = currentInstruction->as<OpLogShadowChickenPrologue>(); |
| if (!m_inlineStackTop->m_inlineCallFrame) |
| addToGraph(LogShadowChickenPrologue, get(bytecode.m_scope)); |
| NEXT_OPCODE(op_log_shadow_chicken_prologue); |
| } |
| |
| case op_log_shadow_chicken_tail: { |
| auto bytecode = currentInstruction->as<OpLogShadowChickenTail>(); |
| if (!m_inlineStackTop->m_inlineCallFrame) { |
| // FIXME: The right solution for inlining is to elide these whenever the tail call |
| // ends up being inlined. |
| // https://bugs.webkit.org/show_bug.cgi?id=155686 |
| addToGraph(LogShadowChickenTail, get(bytecode.m_thisValue), get(bytecode.m_scope)); |
| } |
| NEXT_OPCODE(op_log_shadow_chicken_tail); |
| } |
| |
| case op_unreachable: { |
| flushForTerminal(); |
| addToGraph(Unreachable); |
| LAST_OPCODE(op_unreachable); |
| } |
| |
| default: |
| // Parse failed! This should not happen because the capabilities checker |
| // should have caught it. |
| RELEASE_ASSERT_NOT_REACHED(); |
| return; |
| } |
| } |
| } |
| |
| void ByteCodeParser::linkBlock(BasicBlock* block, Vector<BasicBlock*>& possibleTargets) |
| { |
| ASSERT(!block->isLinked); |
| ASSERT(!block->isEmpty()); |
| Node* node = block->terminal(); |
| ASSERT(node->isTerminal()); |
| |
| switch (node->op()) { |
| case Jump: |
| node->targetBlock() = blockForBytecodeIndex(possibleTargets, BytecodeIndex(node->targetBytecodeOffsetDuringParsing())); |
| break; |
| |
| case Branch: { |
| BranchData* data = node->branchData(); |
| data->taken.block = blockForBytecodeIndex(possibleTargets, BytecodeIndex(data->takenBytecodeIndex())); |
| data->notTaken.block = blockForBytecodeIndex(possibleTargets, BytecodeIndex(data->notTakenBytecodeIndex())); |
| break; |
| } |
| |
| case Switch: { |
| SwitchData* data = node->switchData(); |
| for (unsigned i = node->switchData()->cases.size(); i--;) |
| data->cases[i].target.block = blockForBytecodeIndex(possibleTargets, BytecodeIndex(data->cases[i].target.bytecodeIndex())); |
| data->fallThrough.block = blockForBytecodeIndex(possibleTargets, BytecodeIndex(data->fallThrough.bytecodeIndex())); |
| break; |
| } |
| |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| VERBOSE_LOG("Marking ", RawPointer(block), " as linked (actually did linking)\n"); |
| block->didLink(); |
| } |
| |
| void ByteCodeParser::linkBlocks(Vector<BasicBlock*>& unlinkedBlocks, Vector<BasicBlock*>& possibleTargets) |
| { |
| for (size_t i = 0; i < unlinkedBlocks.size(); ++i) { |
| VERBOSE_LOG("Attempting to link ", RawPointer(unlinkedBlocks[i]), "\n"); |
| linkBlock(unlinkedBlocks[i], possibleTargets); |
| } |
| } |
| |
| ByteCodeParser::InlineStackEntry::InlineStackEntry( |
| ByteCodeParser* byteCodeParser, |
| CodeBlock* codeBlock, |
| CodeBlock* profiledBlock, |
| JSFunction* callee, // Null if this is a closure call. |
| VirtualRegister returnValueVR, |
| VirtualRegister inlineCallFrameStart, |
| int argumentCountIncludingThis, |
| InlineCallFrame::Kind kind, |
| BasicBlock* continuationBlock) |
| : m_byteCodeParser(byteCodeParser) |
| , m_codeBlock(codeBlock) |
| , m_profiledBlock(profiledBlock) |
| , m_continuationBlock(continuationBlock) |
| , m_returnValue(returnValueVR) |
| , m_caller(byteCodeParser->m_inlineStackTop) |
| { |
| { |
| m_exitProfile.initialize(m_profiledBlock->unlinkedCodeBlock()); |
| |
| ConcurrentJSLocker locker(m_profiledBlock->m_lock); |
| m_lazyOperands.initialize(locker, m_profiledBlock->lazyOperandValueProfiles(locker)); |
| |
| // We do this while holding the lock because we want to encourage StructureStubInfo's |
| // to be potentially added to operations and because the profiled block could be in the |
| // middle of LLInt->JIT tier-up in which case we would be adding the info's right now. |
| if (m_profiledBlock->hasBaselineJITProfiling()) |
| m_profiledBlock->getICStatusMap(locker, m_baselineMap); |
| } |
| |
| CodeBlock* optimizedBlock = m_profiledBlock->replacement(); |
| m_optimizedContext.optimizedCodeBlock = optimizedBlock; |
| if (Options::usePolyvariantDevirtualization() && optimizedBlock) { |
| ConcurrentJSLocker locker(optimizedBlock->m_lock); |
| optimizedBlock->getICStatusMap(locker, m_optimizedContext.map); |
| } |
| byteCodeParser->m_icContextStack.append(&m_optimizedContext); |
| |
| int argumentCountIncludingThisWithFixup = std::max<int>(argumentCountIncludingThis, codeBlock->numParameters()); |
| |
| if (m_caller) { |
| // Inline case. |
| ASSERT(codeBlock != byteCodeParser->m_codeBlock); |
| ASSERT(inlineCallFrameStart.isValid()); |
| |
| m_inlineCallFrame = byteCodeParser->m_graph.m_plan.inlineCallFrames()->add(); |
| m_optimizedContext.inlineCallFrame = m_inlineCallFrame; |
| |
| // The owner is the machine code block, and we already have a barrier on that when the |
| // plan finishes. |
| m_inlineCallFrame->baselineCodeBlock.setWithoutWriteBarrier(codeBlock->baselineVersion()); |
| m_inlineCallFrame->setTmpOffset((m_caller->m_inlineCallFrame ? m_caller->m_inlineCallFrame->tmpOffset : 0) + m_caller->m_codeBlock->numTmps()); |
| m_inlineCallFrame->setStackOffset(inlineCallFrameStart.offset() - CallFrame::headerSizeInRegisters); |
| m_inlineCallFrame->argumentCountIncludingThis = argumentCountIncludingThis; |
| RELEASE_ASSERT(m_inlineCallFrame->argumentCountIncludingThis == argumentCountIncludingThis); |
| if (callee) { |
| m_inlineCallFrame->calleeRecovery = ValueRecovery::constant(callee); |
| m_inlineCallFrame->isClosureCall = false; |
| } else |
| m_inlineCallFrame->isClosureCall = true; |
| m_inlineCallFrame->directCaller = byteCodeParser->currentCodeOrigin(); |
| m_inlineCallFrame->argumentsWithFixup.resizeToFit(argumentCountIncludingThisWithFixup); // Set the number of arguments including this, but don't configure the value recoveries, yet. |
| m_inlineCallFrame->kind = kind; |
| |
| m_identifierRemap.resize(codeBlock->numberOfIdentifiers()); |
| m_switchRemap.resize(codeBlock->numberOfSwitchJumpTables()); |
| |
| for (size_t i = 0; i < codeBlock->numberOfIdentifiers(); ++i) { |
| UniquedStringImpl* rep = codeBlock->identifier(i).impl(); |
| unsigned index = byteCodeParser->m_graph.identifiers().ensure(rep); |
| m_identifierRemap[i] = index; |
| } |
| for (unsigned i = 0; i < codeBlock->numberOfSwitchJumpTables(); ++i) { |
| m_switchRemap[i] = byteCodeParser->m_codeBlock->numberOfSwitchJumpTables(); |
| byteCodeParser->m_codeBlock->addSwitchJumpTableFromProfiledCodeBlock(codeBlock->switchJumpTable(i)); |
| } |
| } else { |
| // Machine code block case. |
| ASSERT(codeBlock == byteCodeParser->m_codeBlock); |
| ASSERT(!callee); |
| ASSERT(!returnValueVR.isValid()); |
| ASSERT(!inlineCallFrameStart.isValid()); |
| |
| m_inlineCallFrame = 0; |
| |
| m_identifierRemap.resize(codeBlock->numberOfIdentifiers()); |
| m_switchRemap.resize(codeBlock->numberOfSwitchJumpTables()); |
| for (size_t i = 0; i < codeBlock->numberOfIdentifiers(); ++i) |
| m_identifierRemap[i] = i; |
| for (size_t i = 0; i < codeBlock->numberOfSwitchJumpTables(); ++i) |
| m_switchRemap[i] = i; |
| } |
| |
| m_argumentPositions.resize(argumentCountIncludingThisWithFixup); |
| for (int i = 0; i < argumentCountIncludingThisWithFixup; ++i) { |
| byteCodeParser->m_graph.m_argumentPositions.append(ArgumentPosition()); |
| ArgumentPosition* argumentPosition = &byteCodeParser->m_graph.m_argumentPositions.last(); |
| m_argumentPositions[i] = argumentPosition; |
| } |
| byteCodeParser->m_inlineCallFrameToArgumentPositions.add(m_inlineCallFrame, m_argumentPositions); |
| |
| byteCodeParser->m_inlineStackTop = this; |
| } |
| |
| ByteCodeParser::InlineStackEntry::~InlineStackEntry() |
| { |
| m_byteCodeParser->m_inlineStackTop = m_caller; |
| RELEASE_ASSERT(m_byteCodeParser->m_icContextStack.last() == &m_optimizedContext); |
| m_byteCodeParser->m_icContextStack.removeLast(); |
| } |
| |
| void ByteCodeParser::parseCodeBlock() |
| { |
| clearCaches(); |
| |
| CodeBlock* codeBlock = m_inlineStackTop->m_codeBlock; |
| |
| if (UNLIKELY(m_graph.compilation())) { |
| m_graph.compilation()->addProfiledBytecodes( |
| *m_vm->m_perBytecodeProfiler, m_inlineStackTop->m_profiledBlock); |
| } |
| |
| if (UNLIKELY(Options::dumpSourceAtDFGTime())) { |
| Vector<DeferredSourceDump>& deferredSourceDump = m_graph.m_plan.callback()->ensureDeferredSourceDump(); |
| if (inlineCallFrame()) { |
| DeferredSourceDump dump(codeBlock->baselineVersion(), m_codeBlock, JITType::DFGJIT, inlineCallFrame()->directCaller.bytecodeIndex()); |
| deferredSourceDump.append(dump); |
| } else |
| deferredSourceDump.append(DeferredSourceDump(codeBlock->baselineVersion())); |
| } |
| |
| if (Options::dumpBytecodeAtDFGTime()) { |
| dataLog("Parsing ", *codeBlock); |
| if (inlineCallFrame()) { |
| dataLog( |
| " for inlining at ", CodeBlockWithJITType(m_codeBlock, JITType::DFGJIT), |
| " ", inlineCallFrame()->directCaller); |
| } |
| dataLog( |
| ", isStrictMode = ", codeBlock->ownerExecutable()->isStrictMode(), "\n"); |
| codeBlock->baselineVersion()->dumpBytecode(); |
| } |
| |
| Vector<InstructionStream::Offset, 32> jumpTargets; |
| computePreciseJumpTargets(codeBlock, jumpTargets); |
| if (Options::dumpBytecodeAtDFGTime()) { |
| dataLog("Jump targets: "); |
| CommaPrinter comma; |
| for (unsigned i = 0; i < jumpTargets.size(); ++i) |
| dataLog(comma, jumpTargets[i]); |
| dataLog("\n"); |
| } |
| |
| for (unsigned jumpTargetIndex = 0; jumpTargetIndex <= jumpTargets.size(); ++jumpTargetIndex) { |
| // The maximum bytecode offset to go into the current basicblock is either the next jump target, or the end of the instructions. |
| unsigned limit = jumpTargetIndex < jumpTargets.size() ? jumpTargets[jumpTargetIndex] : codeBlock->instructions().size(); |
| ASSERT(m_currentIndex.offset() < limit); |
| |
| // Loop until we reach the current limit (i.e. next jump target). |
| do { |
| // There may already be a currentBlock in two cases: |
| // - we may have just entered the loop for the first time |
| // - we may have just returned from an inlined callee that had some early returns and |
| // so allocated a continuation block, and the instruction after the call is a jump target. |
| // In both cases, we want to keep using it. |
| if (!m_currentBlock) { |
| m_currentBlock = allocateTargetableBlock(m_currentIndex); |
| |
| // The first block is definitely an OSR target. |
| if (m_graph.numBlocks() == 1) { |
| m_currentBlock->isOSRTarget = true; |
| m_graph.m_roots.append(m_currentBlock); |
| } |
| prepareToParseBlock(); |
| } |
| |
| parseBlock(limit); |
| |
| // We should not have gone beyond the limit. |
| ASSERT(m_currentIndex.offset() <= limit); |
| |
| if (m_currentBlock->isEmpty()) { |
| // This case only happens if the last instruction was an inlined call with early returns |
| // or polymorphic (creating an empty continuation block), |
| // and then we hit the limit before putting anything in the continuation block. |
| ASSERT(m_currentIndex.offset() == limit); |
| makeBlockTargetable(m_currentBlock, m_currentIndex); |
| } else { |
| ASSERT(m_currentBlock->terminal() || (m_currentIndex.offset() == codeBlock->instructions().size() && inlineCallFrame())); |
| m_currentBlock = nullptr; |
| } |
| } while (m_currentIndex.offset() < limit); |
| } |
| |
| // Should have reached the end of the instructions. |
| ASSERT(m_currentIndex.offset() == codeBlock->instructions().size()); |
| |
| VERBOSE_LOG("Done parsing ", *codeBlock, " (fell off end)\n"); |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handlePutByVal(Bytecode bytecode, unsigned instructionSize) |
| { |
| Node* base = get(bytecode.m_base); |
| Node* property = get(bytecode.m_property); |
| Node* value = get(bytecode.m_value); |
| bool isDirect = Bytecode::opcodeID == op_put_by_val_direct; |
| bool compiledAsPutById = false; |
| { |
| unsigned identifierNumber = std::numeric_limits<unsigned>::max(); |
| PutByIdStatus putByIdStatus; |
| { |
| ConcurrentJSLocker locker(m_inlineStackTop->m_profiledBlock->m_lock); |
| ByValInfo* byValInfo = m_inlineStackTop->m_baselineMap.get(CodeOrigin(currentCodeOrigin().bytecodeIndex())).byValInfo; |
| // FIXME: When the bytecode is not compiled in the baseline JIT, byValInfo becomes null. |
| // At that time, there is no information. |
| if (byValInfo |
| && byValInfo->stubInfo |
| && !byValInfo->tookSlowPath |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIdent) |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType) |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) { |
| compiledAsPutById = true; |
| identifierNumber = m_graph.identifiers().ensure(byValInfo->cachedId.impl()); |
| UniquedStringImpl* uid = m_graph.identifiers()[identifierNumber]; |
| |
| if (Symbol* symbol = byValInfo->cachedSymbol.get()) { |
| FrozenValue* frozen = m_graph.freezeStrong(symbol); |
| addToGraph(CheckCell, OpInfo(frozen), property); |
| } else { |
| ASSERT(!uid->isSymbol()); |
| addToGraph(CheckIdent, OpInfo(uid), property); |
| } |
| |
| putByIdStatus = PutByIdStatus::computeForStubInfo( |
| locker, m_inlineStackTop->m_profiledBlock, |
| byValInfo->stubInfo, currentCodeOrigin(), uid); |
| |
| } |
| } |
| |
| if (compiledAsPutById) |
| handlePutById(base, identifierNumber, value, putByIdStatus, isDirect, instructionSize); |
| } |
| |
| if (!compiledAsPutById) { |
| ArrayMode arrayMode = getArrayMode(bytecode.metadata(m_inlineStackTop->m_codeBlock).m_arrayProfile, Array::Write); |
| |
| addVarArgChild(base); |
| addVarArgChild(property); |
| addVarArgChild(value); |
| addVarArgChild(0); // Leave room for property storage. |
| addVarArgChild(0); // Leave room for length. |
| addToGraph(Node::VarArg, isDirect ? PutByValDirect : PutByVal, OpInfo(arrayMode.asWord()), OpInfo(0)); |
| m_exitOK = false; // PutByVal and PutByValDirect must be treated as if they clobber exit state, since FixupPhase may make them generic. |
| } |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handlePutAccessorById(NodeType op, Bytecode bytecode) |
| { |
| Node* base = get(bytecode.m_base); |
| unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_property]; |
| Node* accessor = get(bytecode.m_accessor); |
| addToGraph(op, OpInfo(identifierNumber), OpInfo(bytecode.m_attributes), base, accessor); |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handlePutAccessorByVal(NodeType op, Bytecode bytecode) |
| { |
| Node* base = get(bytecode.m_base); |
| Node* subscript = get(bytecode.m_property); |
| Node* accessor = get(bytecode.m_accessor); |
| addToGraph(op, OpInfo(bytecode.m_attributes), base, subscript, accessor); |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handleNewFunc(NodeType op, Bytecode bytecode) |
| { |
| FunctionExecutable* decl = m_inlineStackTop->m_profiledBlock->functionDecl(bytecode.m_functionDecl); |
| FrozenValue* frozen = m_graph.freezeStrong(decl); |
| Node* scope = get(bytecode.m_scope); |
| set(bytecode.m_dst, addToGraph(op, OpInfo(frozen), scope)); |
| // Ideally we wouldn't have to do this Phantom. But: |
| // |
| // For the constant case: we must do it because otherwise we would have no way of knowing |
| // that the scope is live at OSR here. |
| // |
| // For the non-constant case: NewFunction could be DCE'd, but baseline's implementation |
| // won't be able to handle an Undefined scope. |
| addToGraph(Phantom, scope); |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handleNewFuncExp(NodeType op, Bytecode bytecode) |
| { |
| FunctionExecutable* expr = m_inlineStackTop->m_profiledBlock->functionExpr(bytecode.m_functionDecl); |
| FrozenValue* frozen = m_graph.freezeStrong(expr); |
| Node* scope = get(bytecode.m_scope); |
| set(bytecode.m_dst, addToGraph(op, OpInfo(frozen), scope)); |
| // Ideally we wouldn't have to do this Phantom. But: |
| // |
| // For the constant case: we must do it because otherwise we would have no way of knowing |
| // that the scope is live at OSR here. |
| // |
| // For the non-constant case: NewFunction could be DCE'd, but baseline's implementation |
| // won't be able to handle an Undefined scope. |
| addToGraph(Phantom, scope); |
| } |
| |
| template <typename Bytecode> |
| void ByteCodeParser::handleCreateInternalFieldObject(const ClassInfo* classInfo, NodeType createOp, NodeType newOp, Bytecode bytecode) |
| { |
| CodeBlock* codeBlock = m_inlineStackTop->m_codeBlock; |
| JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic); |
| Node* callee = get(VirtualRegister(bytecode.m_callee)); |
| |
| JSFunction* function = callee->dynamicCastConstant<JSFunction*>(*m_vm); |
| if (!function) { |
| JSCell* cachedFunction = bytecode.metadata(codeBlock).m_cachedCallee.unvalidatedGet(); |
| if (cachedFunction |
| && cachedFunction != JSCell::seenMultipleCalleeObjects() |
| && !m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCell)) { |
| ASSERT(cachedFunction->inherits<JSFunction>(*m_vm)); |
| |
| FrozenValue* frozen = m_graph.freeze(cachedFunction); |
| addToGraph(CheckCell, OpInfo(frozen), callee); |
| |
| function = static_cast<JSFunction*>(cachedFunction); |
| } |
| } |
| |
| if (function) { |
| if (FunctionRareData* rareData = function->rareData()) { |
| if (rareData->allocationProfileWatchpointSet().isStillValid()) { |
| Structure* structure = rareData->internalFunctionAllocationStructure(); |
| if (structure |
| && structure->classInfo() == classInfo |
| && structure->globalObject() == globalObject |
| && rareData->allocationProfileWatchpointSet().isStillValid()) { |
| m_graph.freeze(rareData); |
| m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet()); |
| |
| set(VirtualRegister(bytecode.m_dst), addToGraph(newOp, OpInfo(m_graph.registerStructure(structure)))); |
| // The callee is still live up to this point. |
| addToGraph(Phantom, callee); |
| return; |
| } |
| } |
| } |
| } |
| |
| set(VirtualRegister(bytecode.m_dst), addToGraph(createOp, callee)); |
| } |
| |
| void ByteCodeParser::parse() |
| { |
| // Set during construction. |
| ASSERT(!m_currentIndex.offset()); |
| |
| VERBOSE_LOG("Parsing ", *m_codeBlock, "\n"); |
| |
| InlineStackEntry inlineStackEntry( |
| this, m_codeBlock, m_profiledBlock, 0, VirtualRegister(), VirtualRegister(), |
| m_codeBlock->numParameters(), InlineCallFrame::Call, nullptr); |
| |
| parseCodeBlock(); |
| linkBlocks(inlineStackEntry.m_unlinkedBlocks, inlineStackEntry.m_blockLinkingTargets); |
| |
| if (m_hasAnyForceOSRExits) { |
| BlockSet blocksToIgnore; |
| for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
| if (block->isOSRTarget && block->bytecodeBegin == m_graph.m_plan.osrEntryBytecodeIndex()) { |
| blocksToIgnore.add(block); |
| break; |
| } |
| } |
| |
| { |
| bool isSafeToValidate = false; |
| auto postOrder = m_graph.blocksInPostOrder(isSafeToValidate); // This algorithm doesn't rely on the predecessors list, which is not yet built. |
| bool changed; |
| do { |
| changed = false; |
| for (BasicBlock* block : postOrder) { |
| for (BasicBlock* successor : block->successors()) { |
| if (blocksToIgnore.contains(successor)) { |
| changed |= blocksToIgnore.add(block); |
| break; |
| } |
| } |
| } |
| } while (changed); |
| } |
| |
| InsertionSet insertionSet(m_graph); |
| Operands<VariableAccessData*> mapping(OperandsLike, m_graph.block(0)->variablesAtHead); |
| |
| for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
| if (blocksToIgnore.contains(block)) |
| continue; |
| |
| mapping.fill(nullptr); |
| if (validationEnabled()) { |
| // Verify that it's correct to fill mapping with nullptr. |
| for (unsigned i = 0; i < block->variablesAtHead.size(); ++i) { |
| Node* node = block->variablesAtHead.at(i); |
| RELEASE_ASSERT(!node); |
| } |
| } |
| |
| for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) { |
| { |
| Node* node = block->at(nodeIndex); |
| |
| if (node->hasVariableAccessData(m_graph)) |
| mapping.operand(node->operand()) = node->variableAccessData(); |
| |
| if (node->op() != ForceOSRExit) |
| continue; |
| } |
| |
| NodeOrigin origin = block->at(nodeIndex)->origin; |
| RELEASE_ASSERT(origin.exitOK); |
| |
| ++nodeIndex; |
| |
| { |
| if (validationEnabled()) { |
| // This verifies that we don't need to change any of the successors's predecessor |
| // list after planting the Unreachable below. At this point in the bytecode |
| // parser, we haven't linked up the predecessor lists yet. |
| for (BasicBlock* successor : block->successors()) |
| RELEASE_ASSERT(successor->predecessors.isEmpty()); |
| } |
| |
| auto insertLivenessPreservingOp = [&] (InlineCallFrame* inlineCallFrame, NodeType op, Operand operand) { |
| VariableAccessData* variable = mapping.operand(operand); |
| if (!variable) { |
| variable = newVariableAccessData(operand); |
| mapping.operand(operand) = variable; |
| } |
| |
| Operand argument = unmapOperand(inlineCallFrame, operand); |
| if (argument.isArgument() && !argument.isHeader()) { |
| const Vector<ArgumentPosition*>& arguments = m_inlineCallFrameToArgumentPositions.get(inlineCallFrame); |
| arguments[argument.toArgument()]->addVariable(variable); |
| } |
| insertionSet.insertNode(nodeIndex, SpecNone, op, origin, OpInfo(variable)); |
| }; |
| auto addFlushDirect = [&] (InlineCallFrame* inlineCallFrame, Operand operand) { |
| insertLivenessPreservingOp(inlineCallFrame, Flush, operand); |
| }; |
| auto addPhantomLocalDirect = [&] (InlineCallFrame* inlineCallFrame, Operand operand) { |
| insertLivenessPreservingOp(inlineCallFrame, PhantomLocal, operand); |
| }; |
| flushForTerminalImpl(origin.semantic, addFlushDirect, addPhantomLocalDirect); |
| } |
| |
| while (true) { |
| RELEASE_ASSERT(nodeIndex < block->size()); |
| |
| Node* node = block->at(nodeIndex); |
| |
| node->origin = origin; |
| m_graph.doToChildren(node, [&] (Edge edge) { |
| // We only need to keep data flow edges to nodes defined prior to the ForceOSRExit. The reason |
| // for this is we rely on backwards propagation being able to see the "full" bytecode. To model |
| // this, we preserve uses of a node in a generic way so that backwards propagation can reason |
| // about them. Therefore, we can't remove uses of a node which is defined before the ForceOSRExit |
| // even when we're at a point in the program after the ForceOSRExit, because that would break backwards |
| // propagation's analysis over the uses of a node. However, we don't need this same preservation for |
| // nodes defined after ForceOSRExit, as we've already exitted before those defs. |
| if (edge->hasResult()) |
| insertionSet.insertNode(nodeIndex, SpecNone, Phantom, origin, Edge(edge.node(), UntypedUse)); |
| }); |
| |
| bool isTerminal = node->isTerminal(); |
| |
| node->removeWithoutChecks(); |
| |
| if (isTerminal) { |
| insertionSet.insertNode(nodeIndex, SpecNone, Unreachable, origin); |
| break; |
| } |
| |
| ++nodeIndex; |
| } |
| |
| insertionSet.execute(block); |
| |
| auto nodeAndIndex = block->findTerminal(); |
| RELEASE_ASSERT(nodeAndIndex.node->op() == Unreachable); |
| block->resize(nodeAndIndex.index + 1); |
| break; |
| } |
| } |
| } else if (validationEnabled()) { |
| // Ensure our bookkeeping for ForceOSRExit nodes is working. |
| for (BasicBlock* block : m_graph.blocksInNaturalOrder()) { |
| for (Node* node : *block) |
| RELEASE_ASSERT(node->op() != ForceOSRExit); |
| } |
| } |
| |
| m_graph.determineReachability(); |
| m_graph.killUnreachableBlocks(); |
| |
| for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) { |
| BasicBlock* block = m_graph.block(blockIndex); |
| if (!block) |
| continue; |
| ASSERT(block->variablesAtHead.numberOfLocals() == m_graph.block(0)->variablesAtHead.numberOfLocals()); |
| ASSERT(block->variablesAtHead.numberOfArguments() == m_graph.block(0)->variablesAtHead.numberOfArguments()); |
| ASSERT(block->variablesAtTail.numberOfLocals() == m_graph.block(0)->variablesAtHead.numberOfLocals()); |
| ASSERT(block->variablesAtTail.numberOfArguments() == m_graph.block(0)->variablesAtHead.numberOfArguments()); |
| } |
| |
| m_graph.m_tmps = m_numTmps; |
| m_graph.m_localVars = m_numLocals; |
| m_graph.m_parameterSlots = m_parameterSlots; |
| } |
| |
| void parse(Graph& graph) |
| { |
| ByteCodeParser(graph).parse(); |
| } |
| |
| } } // namespace JSC::DFG |
| |
| #endif |