diff options
author | Erik Verbruggen <erik.verbruggen@qt.io> | 2018-11-23 12:50:27 +0100 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@qt.io> | 2019-03-04 12:55:16 +0000 |
commit | 80f9634f7e3a1c31cda0c804d811c532aeee103b (patch) | |
tree | f9fb44f5f8a952021014109e109411ca29698897 /src/qml/jit | |
parent | 00bcc365403f253e0c6fd54b7b1715ba974679c5 (diff) |
V4: Add IR that can use traced information to JIT
This is the in a series of patches for a JIT that can use traced
information to generate better code. In this patch, traced information
is not used/stored yet. It allows testing the basic infrastructure
without trying to do any optimizations, therefore making it easier to
debug, test, and review.
Change-Id: I589bdadf731c36542331abe64e1b39e305b6723e
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/qml/jit')
-rw-r--r-- | src/qml/jit/jit.pri | 18 | ||||
-rw-r--r-- | src/qml/jit/qv4graph.cpp | 103 | ||||
-rw-r--r-- | src/qml/jit/qv4graph_p.h | 146 | ||||
-rw-r--r-- | src/qml/jit/qv4graphbuilder.cpp | 1739 | ||||
-rw-r--r-- | src/qml/jit/qv4graphbuilder_p.h | 311 | ||||
-rw-r--r-- | src/qml/jit/qv4ir.cpp | 382 | ||||
-rw-r--r-- | src/qml/jit/qv4ir_p.h | 228 | ||||
-rw-r--r-- | src/qml/jit/qv4node.cpp | 215 | ||||
-rw-r--r-- | src/qml/jit/qv4node_p.h | 642 | ||||
-rw-r--r-- | src/qml/jit/qv4operation.cpp | 782 | ||||
-rw-r--r-- | src/qml/jit/qv4operation_p.h | 574 | ||||
-rw-r--r-- | src/qml/jit/qv4runtimesupport_p.h | 255 | ||||
-rw-r--r-- | src/qml/jit/qv4tracingjit.cpp | 79 |
13 files changed, 5474 insertions, 0 deletions
diff --git a/src/qml/jit/jit.pri b/src/qml/jit/jit.pri index 503ce0ebcd..49eb2e8a37 100644 --- a/src/qml/jit/jit.pri +++ b/src/qml/jit/jit.pri @@ -10,3 +10,21 @@ HEADERS += \ $$PWD/qv4baselinejit_p.h \ $$PWD/qv4baselineassembler_p.h \ $$PWD/qv4assemblercommon_p.h + +qtConfig(qml-tracing) { +SOURCES += \ + $$PWD/qv4ir.cpp \ + $$PWD/qv4operation.cpp \ + $$PWD/qv4node.cpp \ + $$PWD/qv4graph.cpp \ + $$PWD/qv4graphbuilder.cpp \ + $$PWD/qv4tracingjit.cpp \ + +HEADERS += \ + $$PWD/qv4ir_p.h \ + $$PWD/qv4operation_p.h \ + $$PWD/qv4runtimesupport_p.h \ + $$PWD/qv4node_p.h \ + $$PWD/qv4graph_p.h \ + $$PWD/qv4graphbuilder_p.h \ +} diff --git a/src/qml/jit/qv4graph.cpp b/src/qml/jit/qv4graph.cpp new file mode 100644 index 0000000000..4025ceb993 --- /dev/null +++ b/src/qml/jit/qv4graph.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4graph_p.h" +#include "qv4operation_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Graph *Graph::create(Function *function) +{ + auto storage = function->pool()->allocate(sizeof(Graph)); + auto g = new (storage) Graph(function); + g->m_undefinedNode = g->createNode(g->opBuilder()->get<Meta::Undefined>()); + g->m_emptyNode = g->createNode(g->opBuilder()->get<Meta::Empty>()); + g->m_nullNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::nullValue())); + g->m_trueNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(true))); + g->m_falseNode = g->createNode(g->opBuilder()->getConstant(QV4::Value::fromBoolean(false))); + return g; +} + +Graph::MemoryPool *Graph::pool() const +{ + return m_function->pool(); +} + +Node *Graph::createNode(const Operation *op, Node *const operands[], size_t opCount, + bool incomplete) +{ + return Node::create(pool(), m_nextNodeId++, op, opCount, operands, incomplete); +} + +Node *Graph::createConstantBoolNode(bool value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromBoolean(value))); +} + +Node *Graph::createConstantIntNode(int value) +{ + return createNode(opBuilder()->getConstant(Primitive::fromInt32(value))); +} + +Graph::Graph(Function *function) + : m_function(function) + , m_opBuilder(OperationBuilder::create(pool())) +{} + +Node *Graph::createConstantHeapNode(Heap::Base *heap) +{ + return createNode(opBuilder()->getConstant(Primitive::fromHeapObject(heap))); +} + +void Graph::addEndInput(Node *n) +{ + if (m_endNode) { + auto newEnd = m_opBuilder->getEnd(m_endNode->operation()->controlInputCount() + 1); + m_endNode->setOperation(newEnd); + m_endNode->addInput(m_function->pool(), n); + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graph_p.h b/src/qml/jit/qv4graph_p.h new file mode 100644 index 0000000000..4706399c94 --- /dev/null +++ b/src/qml/jit/qv4graph_p.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GRAPH_P_H +#define QV4GRAPH_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4node_p.h> + +#include <array> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Function; +class Operation; +class OperationBuilder; + +class Graph final +{ + Q_DISABLE_COPY_MOVE(Graph) + +public: + using MemoryPool = QQmlJS::MemoryPool; + +public: + static Graph *create(Function *function); + ~Graph() = delete; + + MemoryPool *pool() const; + OperationBuilder *opBuilder() const + { return m_opBuilder; } + + Node *createNode(const Operation *op, Node * const operands[] = nullptr, size_t opCount = 0, + bool incomplete = false); + template <typename... Nodes> + Node *createNode(Operation *op, Nodes*... nodes) { + std::array<Node *, sizeof...(nodes)> nodesArray {{ nodes... }}; + return createNode(op, nodesArray.data(), nodesArray.size()); + } + Node *createConstantBoolNode(bool value); + Node *createConstantIntNode(int value); + Node *createConstantHeapNode(Heap::Base *heap); + + Node *undefinedNode() const { return m_undefinedNode; } + Node *emptyNode() const { return m_emptyNode; } + Node *nullNode() const { return m_nullNode; } + Node *trueConstant() const { return m_trueNode; } + Node *falseConstant() const { return m_falseNode; } + + Node *startNode() const { return m_startNode; } + Node *engineNode() const { return m_engineNode; } + Node *functionNode() const { return m_functionNode; } + Node *cppFrameNode() const { return m_cppFrameNode; } + Node *endNode() const { return m_endNode; } + Node *initialFrameState() const { return m_initialFrameState; } + void setStartNode(Node *n) { m_startNode = n; } + void setEngineNode(Node *n) { m_engineNode = n; } + void setFunctionNode(Node *n) { m_functionNode = n; } + void setCppFrameNode(Node *n) { m_cppFrameNode = n; } + void setEndNode(Node *n) { m_endNode = n; } + void setInitialFrameState(Node *n) { m_initialFrameState = n; } + + unsigned nodeCount() const + { return unsigned(m_nextNodeId); } + + void addEndInput(Node *n); + +private: // types and methods + Graph(Function *function); + +private: // fields + Function *m_function; + OperationBuilder *m_opBuilder; + Node::Id m_nextNodeId = 0; + Node *m_undefinedNode = nullptr; + Node *m_emptyNode = nullptr; + Node *m_nullNode = nullptr; + Node *m_trueNode = nullptr; + Node *m_falseNode = nullptr; + Node *m_startNode = nullptr; + Node *m_engineNode = nullptr; + Node *m_functionNode = nullptr; + Node *m_cppFrameNode = nullptr; + Node *m_endNode = nullptr; + Node *m_initialFrameState = nullptr; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPH_P_H diff --git a/src/qml/jit/qv4graphbuilder.cpp b/src/qml/jit/qv4graphbuilder.cpp new file mode 100644 index 0000000000..94b8e86e08 --- /dev/null +++ b/src/qml/jit/qv4graphbuilder.cpp @@ -0,0 +1,1739 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4graphbuilder_p.h" +#include "qv4function_p.h" +#include "qv4lookup_p.h" +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcIRGraphBuilder, "qt.v4.ir.graphbuilder") + +using MemoryPool = QQmlJS::MemoryPool; + +namespace { +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +template <typename T, size_t N> +char (&ArraySizeHelper(const T (&array)[N]))[N]; + +template <typename Array> +inline size_t arraySize(const Array &array) +{ + Q_UNUSED(array); // for MSVC + return sizeof(ArraySizeHelper(array)); +} +} // anonymous namespace + +class GraphBuilder::InterpreterEnvironment +{ +public: + struct FrameState: public QQmlJS::FixedPoolArray<Node *> + { + FrameState(MemoryPool *pool, int totalSlotCount) + : FixedPoolArray(pool, totalSlotCount) + {} + + Node *&unwindHandlerOffset() + { return at(size() - 1); } + + static FrameState *create(MemoryPool *pool, int jsSlotCount) + { + auto totalSlotCount = jsSlotCount; + auto fs = pool->New<FrameState>(pool, totalSlotCount); + return fs; + } + + static FrameState *clone(MemoryPool *pool, FrameState *other) + { + FrameState *fs = create(pool, other->size()); + + for (int i = 0, ei = other->size(); i != ei; ++i) + fs->at(i) = other->at(i); + + return fs; + } + }; + +public: + InterpreterEnvironment(GraphBuilder *graphBuilder, Node *controlDependency) + : m_graphBuilder(graphBuilder) + , m_effectDependency(controlDependency) + , m_controlDependency(controlDependency) + , m_currentFrame(nullptr) + {} + + void createEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + // 1 extra slot for the unwindHandlerOffset + m_currentFrame = FrameState::create(graph()->pool(), int(nRegisters + 1)); + } + + void setupStartEnvironment() + { + Function *f = function(); + QV4::Function *v4Function = f->v4Function(); + const size_t nFormals = v4Function->compiledFunction->nFormals; + const size_t nRegisters = v4Function->compiledFunction->nRegisters; + + createEnvironment(); + + Node *startNode = graph()->startNode(); + auto opB = opBuilder(); + auto create = [&](int index, const char *name) { + m_currentFrame->at(index) = graph()->createNode( + opB->getParam(index, f->addString(QLatin1String(name))), startNode); + }; + create(0, "%function"); + create(1, "%context"); + create(2, "%acc"); + create(3, "%this"); + create(4, "%newTarget"); + create(5, "%argc"); + const quint32_le *formalNameIdx = v4Function->compiledFunction->formalsTable(); + for (size_t i = 0; i < nFormals; ++i, ++formalNameIdx) { + const int slot = int(CallData::HeaderSize() + i); + Q_ASSERT(*formalNameIdx <= quint32(std::numeric_limits<int>::max())); + auto op = opB->getParam( + slot, + f->addString(v4Function->compilationUnit->stringAt(int(*formalNameIdx)))); + Node *argNode = graph()->createNode(op, startNode); + m_currentFrame->at(slot) = argNode; + } + Node *undefinedNode = graph()->undefinedNode(); + Node *emptyNode = graph()->emptyNode(); + const auto firstDeadZoneRegister + = v4Function->compiledFunction->firstTemporalDeadZoneRegister; + const auto registerDeadZoneSize + = v4Function->compiledFunction->sizeOfRegisterTemporalDeadZone; + for (size_t i = CallData::HeaderSize() + nFormals; i < nRegisters; ++i) { + const bool isDead = i >= firstDeadZoneRegister + && i < size_t(firstDeadZoneRegister + registerDeadZoneSize); + m_currentFrame->at(int(i)) = isDead ? emptyNode : undefinedNode; + } + setUnwindHandlerOffset(0); + } + + Function *function() const { return m_graphBuilder->function(); } + Graph *graph() const { return function()->graph(); } + OperationBuilder *opBuilder() const { return graph()->opBuilder(); } + GraphBuilder *graphBuilder() const { return m_graphBuilder; } + + Node *bindAcc(Node *node) + { + bindNodeToSlot(node, CallData::Accumulator); + return node; + } + + Node *accumulator() const + { return slot(CallData::Accumulator); } + + Node *bindNodeToSlot(Node *node, int slot) + { + m_currentFrame->at(size_t(slot)) = node; + return node; + } + + Node *slot(int slot) const + { return m_currentFrame->at(slot); } + + int slotCount() const + { return m_currentFrame->size(); } + + Node *effectDependency() const + { return m_effectDependency; } + + void setEffectDependency(Node *newNode) + { m_effectDependency = newNode; } + + Node *controlDependency() const + { return m_controlDependency; } + + void setControlDependency(Node *newNode) + { m_controlDependency = newNode; } + + Node *createFrameState() + { + return graph()->createNode(graphBuilder()->opBuilder()->getFrameState(slotCount()), + m_currentFrame->begin(), slotCount()); + } + + Node *merge(InterpreterEnvironment *other); + + InterpreterEnvironment *copy() const + { + auto *newEnv = graph()->pool()->New<InterpreterEnvironment>(graphBuilder(), + controlDependency()); + newEnv->setEffectDependency(effectDependency()); + newEnv->m_currentFrame = FrameState::clone(graph()->pool(), m_currentFrame); + return newEnv; + } + + int unwindHandlerOffset() const + { + auto uhOp = m_currentFrame->unwindHandlerOffset()->operation(); + Q_ASSERT(uhOp->kind() == Meta::Constant); + return ConstantPayload::get(*uhOp)->value().int_32(); + } + + void setUnwindHandlerOffset(int newOffset) + { m_currentFrame->unwindHandlerOffset() = graphBuilder()->createConstant(newOffset); } + + FrameState *frameState() const + { return m_currentFrame; } + +private: + GraphBuilder *m_graphBuilder; + Node *m_effectDependency; + Node *m_controlDependency; + FrameState *m_currentFrame; +}; + +namespace { +class InterpreterSubEnvironment final +{ + Q_DISABLE_COPY_MOVE(InterpreterSubEnvironment) + +public: + explicit InterpreterSubEnvironment(GraphBuilder *builder) + : m_builder(builder) + , m_parent(builder->env()->copy()) + {} + + ~InterpreterSubEnvironment() + { m_builder->setEnv(m_parent); } + +private: + GraphBuilder *m_builder; + GraphBuilder::InterpreterEnvironment *m_parent; +}; +} // anonymous namespace + +Node *GraphBuilder::InterpreterEnvironment::merge(InterpreterEnvironment *other) +{ + Q_ASSERT(m_currentFrame->size() == other->m_currentFrame->size()); + + auto gb = graphBuilder(); + Node *mergedControl = gb->mergeControl(controlDependency(), other->controlDependency()); + setControlDependency(mergedControl); + setEffectDependency(gb->mergeEffect(effectDependency(), other->effectDependency(), mergedControl)); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = m_currentFrame->size() - 1; i != ei; ++i) { + //### use lifeness info to trim this! + m_currentFrame->at(i) = gb->mergeValue(m_currentFrame->at(i), + other->m_currentFrame->at(i), + mergedControl); + } + Q_ASSERT(unwindHandlerOffset() >= 0); // specifically: don't crash + return mergedControl; +} + +void GraphBuilder::buildGraph(IR::Function *function) +{ + const char *code = function->v4Function()->codeData; + uint len = function->v4Function()->compiledFunction->codeSize; + + GraphBuilder builder(function); + builder.startGraph(); + + InterpreterEnvironment initial(&builder, function->graph()->startNode()); + initial.setupStartEnvironment(); + builder.setEnv(&initial); + builder.graph()->setInitialFrameState(initial.createFrameState()); + builder.decode(code, len); + builder.endGraph(); +}; + +GraphBuilder::GraphBuilder(IR::Function *function) + : m_func(function) + , m_graph(function->graph()) + , m_currentEnv(nullptr) +{ + for (unsigned i = 0, ei = m_func->v4Function()->compiledFunction->nLabelInfos; i != ei; ++i) { + unsigned label = m_func->v4Function()->compiledFunction->labelInfoTable()[i]; + m_labelInfos.emplace_back(label); + if (lcIRGraphBuilder().isDebugEnabled()) { + const LabelInfo &li = m_labelInfos.back(); + qCDebug(lcIRGraphBuilder) << "Loop start at" << li.labelOffset; + } + } +} + +void GraphBuilder::startGraph() +{ + size_t nValuesOut = 1 + CallData::HeaderSize() + + m_func->v4Function()->compiledFunction->nFormals; + Node *start = m_graph->createNode(opBuilder()->getStart(uint16_t(nValuesOut)), nullptr, 0); + m_func->nodeInfo(start)->setBytecodeOffsets(0, 0); + m_graph->setStartNode(start); + m_graph->setEngineNode(m_graph->createNode(opBuilder()->get<Meta::Engine>(), &start, 1)); + auto frame = m_graph->createNode(opBuilder()->get<Meta::CppFrame>(), &start, 1); + m_graph->setCppFrameNode(frame); + m_graph->setFunctionNode(m_graph->createNode(opBuilder()->get<Meta::Function>(), + &frame, 1)); +} + +void GraphBuilder::endGraph() +{ + const auto inputCount = uint16_t(m_exitControls.size()); + Node **inputs = &m_exitControls.front(); + Q_ASSERT(m_graph->endNode() == nullptr); + m_graph->setEndNode(m_graph->createNode(opBuilder()->getEnd(inputCount), inputs, inputCount)); +} + +Node *GraphBuilder::bindAcc(Node *n) +{ + return env()->bindAcc(n); +} + +/* IMPORTANT!!! + * + * This might change the success environment, so don't call: + * env()->bindAcc(createNode(...)) + * because the binding should only happen on success, but the call to env() will get the + * environment from *before* the new success environment was created. Instead, do: + * bindAcc(createNode(....)) + */ +Node *GraphBuilder::createAndLinkNode(Operation *op, Node *operands[], size_t opCount, + bool incomplete) +{ + Q_ASSERT(op->effectInputCount() < 2); + Q_ASSERT(op->controlInputCount() < 2); + + QVarLengthArray<Node *, 32> inputs(static_cast<int>(opCount)); + std::copy_n(operands, opCount, inputs.data()); + + if (op->effectInputCount() == 1) + inputs.append(env()->effectDependency()); + if (op->controlInputCount() == 1) + inputs.append(env()->controlDependency()); + if (op->hasFrameStateInput()) + inputs.append(env()->createFrameState()); + + Node *node = m_graph->createNode(op, inputs.data(), inputs.size(), incomplete); + + if (op->needsBytecodeOffsets()) { + m_func->nodeInfo(node)->setBytecodeOffsets(currentInstructionOffset(), + nextInstructionOffset()); + } + + if (op->effectOutputCount() > 0) + env()->setEffectDependency(node); + if (op->controlOutputCount() > 0) + env()->setControlDependency(node); + + if (op->canThrow() && env()->unwindHandlerOffset()) { + InterpreterSubEnvironment successEnv(this); + Node *control = env()->controlDependency(); + control = m_graph->createNode(opBuilder()->get<Meta::OnException>(), &control, 1); + env()->setControlDependency(control); + auto unwindHandlerOffset = env()->unwindHandlerOffset(); + mergeIntoSuccessor(unwindHandlerOffset); + } + + return node; +} + +Node *GraphBuilder::createNode(Operation *op, bool incomplete) +{ + return createAndLinkNode(op, nullptr, 0, incomplete); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1) +{ + Node *buf[] = { n1 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2) +{ + Node *buf[] = { n1, n2 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3) +{ + Node *buf[] = { n1, n2, n3 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4) +{ + Node *buf[] = { n1, n2, n3, n4 }; + return createAndLinkNode(op, buf, arraySize(buf)); +} + +Node *GraphBuilder::createRegion(unsigned nControlInputs) +{ + return createNode(opBuilder()->getRegion(nControlInputs), true); +} + +Node *GraphBuilder::createIfTrue() +{ + return createNode(opBuilder()->get<Meta::IfTrue>()); +} + +Node *GraphBuilder::createIfFalse() +{ + return createNode(opBuilder()->get<Meta::IfFalse>()); +} + +Node *GraphBuilder::createConstant(int v) +{ + return m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(v))); +} + +Node *GraphBuilder::createPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createEffectPhi(unsigned nInputs, Node *input, Node *control) +{ + auto phiOp = opBuilder()->getEffectPhi(nInputs); + QVarLengthArray<Node *, 32> buffer(int(nInputs + 1)); + std::fill_n(buffer.data(), nInputs, input); + buffer[int(nInputs)] = control; + return m_graph->createNode(phiOp, buffer.data(), nInputs + 1, true); +} + +Node *GraphBuilder::createHandleUnwind(int offset) +{ + return createNode(opBuilder()->getHandleUnwind(offset)); +} + +Node *GraphBuilder::mergeControl(Node *c1, Node *c2) +{ + if (c1->operation()->kind() == Meta::Region) { + const unsigned nInputs = c1->operation()->controlInputCount() + 1; + c1->addInput(m_graph->pool(), c2); + c1->setOperation(opBuilder()->getRegion(nInputs)); + return c1; + } + auto op = opBuilder()->getRegion(2); + Node *inputs[] = { c1, c2 }; + return m_graph->createNode(op, inputs, 2); +} + +Node *GraphBuilder::mergeEffect(Node *e1, Node *e2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (e1->operation()->kind() == Meta::EffectPhi && e1->controlInput() == control) { + e1->insertInput(m_graph->pool(), nInputs - 1, e2); + e1->setOperation(opBuilder()->getEffectPhi(nInputs)); + return e1; + } + + if (e1 != e2) { + Node *phi = createEffectPhi(nInputs, e1, control); + phi->replaceInput(nInputs - 1, e2); + return phi; + } + + return e1; +} + +Node *GraphBuilder::mergeValue(Node *v1, Node *v2, Node *control) +{ + const unsigned nInputs = control->operation()->controlInputCount(); + if (v1->operation()->kind() == Meta::Phi && v1->controlInput() == control) { + v1->insertInput(m_graph->pool(), nInputs - 1, v2); + v1->setOperation(opBuilder()->getPhi(nInputs)); + return v1; + } + + if (v1 != v2) { + Node *phi = createPhi(nInputs, v1, control); + phi->replaceInput(nInputs - 1, v2); + return phi; + } + + return v1; +} + +Node *GraphBuilder::createToBoolean(Node *input) +{ + return createNode(opBuilder()->get<Meta::ToBoolean>(), input); +} + +void GraphBuilder::populate(VarArgNodes &args, int argc, int argv) +{ + for (int i = 0; i < argc; ++i) + args.append(env()->slot(argv + i)); + Q_ASSERT(argc >= 0 && argc <= std::numeric_limits<uint16_t>::max()); +} + +void GraphBuilder::queueFunctionExit(Node *exitNode) +{ + m_exitControls.push_back(exitNode); + setEnv(nullptr); +} + +Node *GraphBuilder::mergeIntoSuccessor(int offset) +{ + InterpreterEnvironment *&successorEnvironment = m_envForOffset[offset]; + + Node *region = nullptr; + if (successorEnvironment == nullptr) { + region = createRegion(1); + successorEnvironment = env(); + } else { + // Merge any values which are live coming into the successor. + region = successorEnvironment->merge(env()); + } + setEnv(nullptr); + return region; +} + +const GraphBuilder::LabelInfo *GraphBuilder::labelInfoAt(unsigned offset) const +{ + for (const LabelInfo &li : m_labelInfos) { + if (li.labelOffset == offset) + return &li; + } + return nullptr; +} + +const GraphBuilder::LabelInfo *GraphBuilder::isLoopStart(unsigned offset) const +{ + if (auto li = labelInfoAt(offset)) { + //### in the future, check if this is a loop start, or some other label + return li; + } + + return nullptr; +} + +void GraphBuilder::handleLoopStart(const LabelInfo &labelInfo) +{ + Q_ASSERT(env() != nullptr); + + // We unconditionally insert a region node with phi nodes here. Now there might already be + // such a node, (e.g. the region after an if-then-else), but for simplicity we ignore that. + // A subsequent pass will fold/remove chains of Region nodes. + //### FIXME: add a DCE pass + + const auto offset = int(labelInfo.labelOffset); + Node *control = createRegion(1); + env()->setControlDependency(control); + Node *effect = createEffectPhi(1, env()->effectDependency(), control); + env()->setEffectDependency(effect); + + // insert/update phi nodes, but not for the unwind handler: + for (int i = 0, ei = env()->slotCount() - 1; i != ei; ++i) { + //### use lifeness info to trim this further! + if (i == CallData::Accumulator) + continue; // should never be alive on loop entry + env()->bindNodeToSlot(createPhi(1, env()->slot(i), control), i); + } + + m_envForOffset.insert(offset, env()->copy()); +} + +void GraphBuilder::startUnwinding() +{ + if (int target = env()->unwindHandlerOffset()) { + mergeIntoSuccessor(target); + } else { + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } +} + +void GraphBuilder::generate_Ret() +{ + Node* control = createNode(opBuilder()->get<Meta::Return>(), env()->accumulator()); + queueFunctionExit(control); +} + +void GraphBuilder::generate_Debug() { Q_UNREACHABLE(); } + +void GraphBuilder::generate_LoadConst(int index) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadZero() +{ + bindAcc(createConstant(0)); +} + +void GraphBuilder::generate_LoadTrue() +{ + bindAcc(m_graph->trueConstant()); +} + +void GraphBuilder::generate_LoadFalse() +{ + bindAcc(m_graph->falseConstant()); +} + +void GraphBuilder::generate_LoadNull() +{ + bindAcc(m_graph->nullNode()); +} + +void GraphBuilder::generate_LoadUndefined() +{ + bindAcc(m_graph->undefinedNode()); +} + +void GraphBuilder::generate_LoadInt(int value) +{ + bindAcc(m_graph->createNode(opBuilder()->getConstant(Primitive::fromInt32(value)))); +} + +void GraphBuilder::generate_MoveConst(int constIndex, int destTemp) +{ + auto func = function()->v4Function(); + Value v = func->compilationUnit->constants[constIndex]; + env()->bindNodeToSlot(createNode(opBuilder()->getConstant(v)), destTemp); +} + +void GraphBuilder::generate_LoadReg(int reg) +{ + bindAcc(env()->slot(reg)); +} + +void GraphBuilder::generate_StoreReg(int reg) +{ + Node *n = env()->accumulator(); + if (reg == CallData::This) + n = createNode(opBuilder()->get<Meta::StoreThis>(), n); + env()->bindNodeToSlot(n, reg); +} + +void GraphBuilder::generate_MoveReg(int srcReg, int destReg) +{ + env()->bindNodeToSlot(env()->slot(srcReg), destReg); +} + +void GraphBuilder::generate_LoadImport(int index) +{ + auto func = function()->v4Function(); + Value v = *func->compilationUnit->imports[index]; + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_LoadLocal(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(0), + createConstant(index))); +} + +void GraphBuilder::generate_StoreLocal(int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(0), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadScopedLocal(int scope, int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::ScopedLoad>(), + createConstant(scope), + createConstant(index))); +} + +void GraphBuilder::generate_StoreScopedLocal(int scope, int index) +{ + createNode(opBuilder()->get<Meta::ScopedStore>(), + createConstant(scope), + createConstant(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadRuntimeString(int stringId) +{ + auto func = function()->v4Function(); + Value v = Value::fromHeapObject(func->compilationUnit->runtimeStrings[stringId]); + bindAcc(createNode(opBuilder()->getConstant(v))); +} + +void GraphBuilder::generate_MoveRegExp(int regExpId, int destReg) +{ + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::LoadRegExp>(), + createConstant(regExpId)), destReg); +} + +void GraphBuilder::generate_LoadClosure(int value) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadClosure>(), + createConstant(value))); +} + +void GraphBuilder::generate_LoadName(int name, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadName>(), + createConstant(name))); +} + +void GraphBuilder::generate_LoadGlobalLookup(int index, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadGlobalLookup>(), createConstant(index))); +} + +void GraphBuilder::generate_StoreNameSloppy(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameSloppy>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_StoreNameStrict(int name) +{ + createNode(opBuilder()->get<Meta::JSStoreNameStrict>(), createConstant(name), env()->accumulator()); +} + +void GraphBuilder::generate_LoadElement(int base, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadElement>(), + env()->slot(base), + env()->accumulator())); +} + +void GraphBuilder::generate_StoreElement(int base, int index, int /*traceSlot*/) +{ + createNode(opBuilder()->get<Meta::JSStoreElement>(), + env()->slot(base), + env()->slot(index), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadProperty(int name, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSLoadProperty>(), + env()->accumulator(), + createConstant(name)); + bindAcc(n); +} + +void GraphBuilder::generate_GetLookup(int index, int /*traceSlot*/) +{ + Node *n = createNode(opBuilder()->get<Meta::JSGetLookup>(), + env()->accumulator(), + createConstant(index)); + bindAcc(n); +} + +void GraphBuilder::generate_StoreProperty(int name, int base) +{ + createNode(opBuilder()->get<Meta::JSStoreProperty>(), + env()->slot(base), + createConstant(name), + env()->accumulator()); +} + +void GraphBuilder::generate_SetLookup(int index, int base) +{ + + function()->v4Function()->isStrict() + ? createNode(opBuilder()->get<Meta::JSSetLookupStrict>(), env()->slot(base), + createConstant(index), env()->accumulator()) + : createNode(opBuilder()->get<Meta::JSSetLookupSloppy>(), env()->slot(base), + createConstant(index), env()->accumulator()); +} + +void GraphBuilder::generate_LoadSuperProperty(int property) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperProperty>(), + env()->slot(property))); +} + +void GraphBuilder::generate_StoreSuperProperty(int property) +{ + createNode(opBuilder()->get<Meta::JSStoreSuperProperty>(), + createConstant(property), + env()->accumulator()); +} + +void GraphBuilder::generate_StoreScopeObjectProperty(int base, int propertyIndex) +{ + createNode(opBuilder()->get<Meta::QMLStoreScopeObjectProperty>(), + env()->slot(base), + createConstant(propertyIndex), + env()->accumulator()); +} + +void GraphBuilder::generate_StoreContextObjectProperty(int base, int propertyIndex) +{ + createNode(opBuilder()->get<Meta::QMLStoreContextObjectProperty>(), + env()->slot(base), + createConstant(propertyIndex), + env()->accumulator()); +} + +void GraphBuilder::generate_LoadScopeObjectProperty(int propertyIndex, int base, + int captureRequired) +{ + bindAcc(createNode(opBuilder()->get<Meta::QMLLoadScopeObjectProperty>(), + env()->slot(base), + createConstant(propertyIndex), + createConstant(captureRequired))); +} + +void GraphBuilder::generate_LoadContextObjectProperty(int propertyIndex, int base, + int captureRequired) +{ + bindAcc(createNode(opBuilder()->get<Meta::QMLLoadContextObjectProperty>(), + env()->slot(base), + createConstant(propertyIndex), + createConstant(captureRequired))); +} + +void GraphBuilder::generate_LoadIdObject(int index, int base) +{ + bindAcc(createNode(opBuilder()->get<Meta::QMLLoadIdObject>(), + env()->slot(base), + createConstant(index))); +} + +void GraphBuilder::generate_Yield() { Q_UNREACHABLE(); } +void GraphBuilder::generate_YieldStar() { Q_UNREACHABLE(); } +void GraphBuilder::generate_Resume(int /*offset*/) { Q_UNREACHABLE(); } + +void GraphBuilder::finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv) +{ + populate(args, argc, argv); + bindAcc(createAndLinkNode(opBuilder()->getJSVarArgsCall(kind, uint16_t(args.size())), + args.data(), size_t(args.size()))); +} + +void GraphBuilder::generate_CallValue(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + finalizeCall(Meta::JSCallValue, args, argc, argv); +} + +void GraphBuilder::generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(name)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithReceiver, args, argc, argv); +} + +void GraphBuilder::generate_CallProperty(int name, int base, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(name)); + finalizeCall(Meta::JSCallProperty, args, argc, argv); +} + +void GraphBuilder::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(lookupIndex)); + finalizeCall(Meta::JSCallLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallElement(int base, int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(env()->slot(index)); + finalizeCall(Meta::JSCallElement, args, argc, argv); +} + +void GraphBuilder::generate_CallName(int name, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(name)); + finalizeCall(Meta::JSCallName, args, argc, argv); +} + +void GraphBuilder::generate_CallPossiblyDirectEval(int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + finalizeCall(Meta::JSCallPossiblyDirectEval, args, argc, argv); +} + +void GraphBuilder::generate_CallGlobalLookup(int index, int argc, int argv, int /*traceSlot*/) +{ + VarArgNodes args; + args.append(createConstant(index)); + finalizeCall(Meta::JSCallGlobalLookup, args, argc, argv); +} + +void GraphBuilder::generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(propIdx)); + finalizeCall(Meta::QMLCallScopeObjectProperty, args, argc, argv); +} + +void GraphBuilder::generate_CallContextObjectProperty(int propIdx, int base, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(base)); + args.append(createConstant(propIdx)); + finalizeCall(Meta::QMLCallContextObjectProperty, args, argc, argv); +} + +void GraphBuilder::generate_SetUnwindHandler(int offset) +{ + m_currentUnwindHandlerOffset = offset ? absoluteOffset(offset) : 0; + env()->setUnwindHandlerOffset(m_currentUnwindHandlerOffset); +} + +void GraphBuilder::generate_UnwindDispatch() +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + { + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + startUnwinding(); + } + + createIfFalse(); + + const auto unwindHandlerOffset = env()->unwindHandlerOffset(); + const auto fallthroughSuccessor = nextInstructionOffset(); + auto nContinuations = m_func->unwindLabelOffsets().size() + 1; + if (unwindHandlerOffset) + ++nContinuations; + Q_ASSERT(nContinuations <= std::numeric_limits<unsigned>::max()); + createNode(opBuilder()->getUnwindDispatch(unsigned(nContinuations), unwindHandlerOffset, + fallthroughSuccessor)); + + { + InterpreterSubEnvironment fallthroughEnv(this); + mergeIntoSuccessor(fallthroughSuccessor); + } + + if (unwindHandlerOffset) { + InterpreterSubEnvironment unwindHandlerEnv(this); + createHandleUnwind(unwindHandlerOffset); + mergeIntoSuccessor(unwindHandlerOffset); + } + + for (int unwindLabelOffset : m_func->unwindLabelOffsets()) { + if (unwindLabelOffset <= currentInstructionOffset()) + continue; + InterpreterSubEnvironment unwindLabelEnv(this); + createHandleUnwind(unwindLabelOffset); + mergeIntoSuccessor(unwindLabelOffset); + } + + setEnv(nullptr); +} + +void GraphBuilder::generate_UnwindToLabel(int level, int offset) +{ + //### For de-optimization, the relative offset probably also needs to be stored + int unwinder = absoluteOffset(offset); + createNode(opBuilder()->get<Meta::UnwindToLabel>(), + createConstant(level), + createConstant(unwinder)); + m_func->addUnwindLabelOffset(unwinder); + startUnwinding(); +} + +void GraphBuilder::generate_DeadTemporalZoneCheck(int name) +{ + Node *check = createNode(opBuilder()->get<Meta::IsEmpty>(), env()->accumulator()); + createNode(opBuilder()->get<Meta::Branch>(), check); + + { //### it's probably better to handle this by de-optimizing + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + createNode(opBuilder()->get<Meta::ThrowReferenceError>(), + createConstant(name)); + startUnwinding(); + } + + createIfFalse(); +} + +void GraphBuilder::generate_ThrowException() +{ + createNode(opBuilder()->get<Meta::Throw>(), env()->accumulator()); + startUnwinding(); +} + +void GraphBuilder::generate_GetException() +{ + bindAcc(createNode(opBuilder()->get<Meta::GetException>())); +} + +void GraphBuilder::generate_SetException() +{ + createNode(opBuilder()->get<Meta::SetException>(), + env()->accumulator()); +} + +void GraphBuilder::generate_CreateCallContext() +{ + createNode(opBuilder()->get<Meta::JSCreateCallContext>()); +} + +void GraphBuilder::generate_PushCatchContext(int index, int name) +{ + createNode(opBuilder()->get<Meta::JSCreateCatchContext>(), + createConstant(index), + createConstant(name)); +} + +void GraphBuilder::generate_PushWithContext() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateWithContext>(), + env()->accumulator())); +} + +void GraphBuilder::generate_PushBlockContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateBlockContext>(), + createConstant(index)); +} + +void GraphBuilder::generate_CloneBlockContext() +{ + createNode(opBuilder()->get<Meta::JSCloneBlockContext>()); +} + +void GraphBuilder::generate_PushScriptContext(int index) +{ + createNode(opBuilder()->get<Meta::JSCreateScriptContext>(), createConstant(index)); +} + +void GraphBuilder::generate_PopScriptContext() +{ + createNode(opBuilder()->get<Meta::JSPopScriptContext>()); +} + +void GraphBuilder::generate_PopContext() +{ + createNode(opBuilder()->get<Meta::PopContext>()); +} + +void GraphBuilder::generate_GetIterator(int iterator) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetIterator>(), + env()->accumulator(), + createConstant(iterator))); +} + +void GraphBuilder::generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, + int resultSlot) +{ + // See generate_IteratorNext for why this method exists. + + // check that no-one messed around with the operation and made it throwing + Q_ASSERT(iterationNode->operation()->controlOutputCount() == 1); + + // check that it's in the effect chain, because HasException relies on that + Q_ASSERT(iterationNode->operation()->effectOutputCount() == 1); + + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::SelectOutput>(), + iterationNode, + createConstant(1), + graph()->undefinedNode()), + resultSlot); + // Note: the following will NOT set the accumulator, because it contains the return value of + // the runtime call! + Node *ehCheck = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), ehCheck); + + { // EH path: + InterpreterSubEnvironment subEnvironment(this); + createIfTrue(); + if (auto ehOffset = env()->unwindHandlerOffset()) { + // Ok, there is an exception handler, so go there: + mergeIntoSuccessor(ehOffset); + } else { + // No Exception Handler, so keep the exception set in the engine, and leave the function + // a.s.a.p.: + bindAcc(graph()->undefinedNode()); + generate_Ret(); + } + } + + // Normal control flow: + createIfFalse(); +} + +void GraphBuilder::generate_IteratorNext(int value, int done) +{ + // The way we model exceptions in the graph is that a runtime function will either succeed and + // return a value, or it fails and throws an exception. If it throws, the return value is not + // used because the method did not complete normally, and therefore it might be tainted. + // + // This is a problem for (and only for) IteratorNext and IteratorNextForYieldStart. + // + // What would happen in the normal case, is that the return value (done) is not used/assigned + // when IteratorNext throws, because the exception handling path is chosen. However, the + // interpreter *does* assign it, and will only check for an exception *after* that assignment. + // + // So, in order to work around this odd-duck behavior, we mark the operation as NoThrow, + // override the runtime method and flag it to not throw, and insert extra exception check nodes + // after the SelectOutput that follows the IteratorNext(ForYieldStar). + // + // Also note that the IteratorNext and IteratorNextForYieldStar are the only operations that + // have an inout parameter, and thus require a SelectOutput node to retrieve this. + + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNext>(), + env()->accumulator(), + graph()->undefinedNode()); + bindAcc(n); + env()->bindNodeToSlot(n, done); + generate_IteratorNextAndFriends_TrailingStuff(n, value); +} + +void GraphBuilder::generate_IteratorNextForYieldStar(int iterator, int object) +{ + // Please, PLEASE read the comment in generate_IteratorNext. + Node *n = createNode(opBuilder()->get<Meta::JSIteratorNextForYieldStar>(), + env()->accumulator(), + env()->slot(iterator), + graph()->undefinedNode()); + // Note: the following is a tiny bit different from what generate_IteratorNext does. + bindAcc(n); + generate_IteratorNextAndFriends_TrailingStuff(n, object); +} + +void GraphBuilder::generate_IteratorClose(int done) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIteratorClose>(), + env()->accumulator(), + env()->slot(done))); +} + +void GraphBuilder::generate_DestructureRestElement() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDestructureRestElement>(), + env()->accumulator())); +} + +void GraphBuilder::generate_DeleteProperty(int base, int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteProperty>(), + env()->slot(base), + env()->slot(index))); +} + +void GraphBuilder::generate_DeleteName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeleteName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofName(int name) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofName>(), + createConstant(name))); +} + +void GraphBuilder::generate_TypeofValue() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSTypeofValue>(), env()->accumulator())); +} + +void GraphBuilder::generate_DeclareVar(int varName, int isDeletable) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDeclareVar>(), + createConstant(isDeletable), + createConstant(varName))); +} + +void GraphBuilder::generate_DefineArray(int argc, int argv) +{ + VarArgNodes args; + finalizeCall(Meta::JSDefineArray, args, argc, argv); +} + +void GraphBuilder::generate_DefineObjectLiteral(int internalClassId, int argc, int argv) +{ + VarArgNodes args; + args.append(createConstant(internalClassId)); + finalizeCall(Meta::JSDefineObjectLiteral, args, argc, argv); +} + +void GraphBuilder::generate_CreateClass(int classIndex, int heritage, int computedNames) +{ + int argc = 0; + int argv = computedNames; + + const QV4::CompiledData::Class *cls = function()->v4Function()->compilationUnit->unitData() + ->classAt(classIndex); + const CompiledData::Method *methods = cls->methodTable(); + for (uint i = 0; i < cls->nStaticMethods + cls->nMethods; ++i) { + if (methods[i].name == std::numeric_limits<unsigned>::max()) + ++argc; + } + + VarArgNodes args; + args.append(createConstant(classIndex)); + args.append(env()->slot(heritage)); + finalizeCall(Meta::JSCreateClass, args, argc, argv); +} + +void GraphBuilder::generate_CreateMappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateMappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateUnmappedArgumentsObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateUnmappedArgumentsObject>())); +} + +void GraphBuilder::generate_CreateRestParameter(int argIndex) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSCreateRestParameter>(), + createConstant(argIndex))); +} + +void GraphBuilder::generate_ConvertThisToObject() +{ + Node* control = createNode(opBuilder()->get<Meta::JSThisToObject>(), + env()->slot(CallData::This)); + env()->bindNodeToSlot(control, CallData::This); +} + +void GraphBuilder::generate_LoadSuperConstructor() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSLoadSuperConstructor>(), + env()->slot(CallData::Function))); +} + +void GraphBuilder::generate_ToObject() +{ + bindAcc(createNode(opBuilder()->get<Meta::ToObject>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int /*traceSlot*/) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + finalizeCall(Meta::JSCallWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_TailCall(int func, int thisObject, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->slot(thisObject)); + populate(args, argc, argv); + Node *n = createAndLinkNode(opBuilder()->getJSTailCall(uint16_t(args.size())), args.data(), + size_t(args.size())); + queueFunctionExit(n); +} + +void GraphBuilder::generate_Construct(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstruct, args, argc, argv); +} + +void GraphBuilder::generate_ConstructWithSpread(int func, int argc, int argv) +{ + VarArgNodes args; + args.append(env()->slot(func)); + args.append(env()->accumulator()); + finalizeCall(Meta::JSConstructWithSpread, args, argc, argv); +} + +void GraphBuilder::generate_Jump(int offset) +{ + auto jumpTarget = absoluteOffset(offset); + mergeIntoSuccessor(jumpTarget); +} + +void GraphBuilder::generate_JumpTrue(int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(env()->accumulator())); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfTrue(); + mergeIntoSuccessor(jumpTarget); + } + + createIfFalse(); +} + +void GraphBuilder::generate_JumpFalse(int traceSlot, int offset) +{ + generate_JumpFalse(env()->accumulator(), traceSlot, offset); +} + +void GraphBuilder::generate_JumpFalse(Node *condition, int /*traceSlot*/, int offset) +{ + createNode(opBuilder()->get<Meta::Branch>(), createToBoolean(condition)); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNoException(int offset) +{ + auto e = createNode(opBuilder()->get<Meta::HasException>(), graph()->engineNode()); + createNode(opBuilder()->get<Meta::Branch>(), e); + + { + InterpreterSubEnvironment subEnvironment(this); + auto jumpTarget = absoluteOffset(offset); + createIfFalse(); + mergeIntoSuccessor(jumpTarget); + } + + createIfTrue(); +} + +void GraphBuilder::generate_JumpNotUndefined(int offset) +{ + Node *condition = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->accumulator(), + graph()->undefinedNode()); + generate_JumpFalse(condition, NoTraceSlot, offset); +} + +void GraphBuilder::generate_CmpEqNull() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSEqual>(), + env()->accumulator(), + graph()->nullNode())); +} + +void GraphBuilder::generate_CmpNeNull() +{ + generate_CmpEqNull(); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEqInt(int lhs) +{ + auto left = createConstant(lhs); + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + left, + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNeInt(int lhs) +{ + generate_CmpEqInt(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpEq(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpNe(int lhs) +{ + generate_CmpEq(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpGt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpGe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSGreaterEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLt(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessThan>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpLe(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSLessEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictEqual(int lhs) +{ + Node* control = createNode(opBuilder()->get<Meta::JSStrictEqual>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_CmpStrictNotEqual(int lhs) +{ + generate_CmpStrictEqual(lhs); + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpIn(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSIn>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_CmpInstanceOf(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSInstanceOf>(), env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UNot() +{ + bindAcc(createNode(opBuilder()->get<Meta::BooleanNot>(), + createToBoolean(env()->accumulator()))); +} + +void GraphBuilder::generate_UPlus() +{ + Node* control = createNode(opBuilder()->get<Meta::JSToNumber>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UMinus(int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSNegate>(), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_UCompl() +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(-1))); +} + +void GraphBuilder::generate_Increment(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSAdd>(), + env()->accumulator(), + createConstant(1))); +} + + +void GraphBuilder::generate_Decrement(int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->accumulator(), + createConstant(1))); +} + +void GraphBuilder::generate_Add(int lhs, int /*traceSlot*/) +{ + Node* control = createNode(opBuilder()->get<Meta::JSAdd>(), + env()->slot(lhs), + env()->accumulator()); + bindAcc(control); +} + +void GraphBuilder::generate_BitAnd(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitOr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_BitXor(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_UShr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shr(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Shl(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->slot(lhs), + env()->accumulator())); +} + + +void GraphBuilder::generate_BitAndConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitAnd>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitOrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitOr>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_BitXorConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSBitXor>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_UShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSUnsignedShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShrConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftRight>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_ShlConst(int rhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSShiftLeft>(), + env()->accumulator(), + createConstant(rhs))); +} + +void GraphBuilder::generate_Exp(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSExponentiate>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mul(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSMultiply>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Div(int lhs) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSDivide>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Mod(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSModulo>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_Sub(int lhs, int /*traceSlot*/) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSSubtract>(), + env()->slot(lhs), + env()->accumulator())); +} + +void GraphBuilder::generate_LoadQmlContext(int result) +{ + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::QMLLoadContext>()), result); +} + +void GraphBuilder::generate_LoadQmlImportedScripts(int result) +{ + env()->bindNodeToSlot(createNode(opBuilder()->get<Meta::QMLLoadImportedScripts>()), + result); +} + +void GraphBuilder::generate_InitializeBlockDeadTemporalZone(int firstReg, int count) +{ + for (int reg = firstReg; reg < firstReg + count; ++reg) + env()->bindNodeToSlot(graph()->emptyNode(), reg); +} + +void GraphBuilder::generate_ThrowOnNullOrUndefined() +{ + createNode(opBuilder()->get<Meta::JSThrowOnNullOrUndefined>(), + env()->accumulator()); +} + +void GraphBuilder::generate_GetTemplateObject(int index) +{ + bindAcc(createNode(opBuilder()->get<Meta::JSGetTemplateObject>(), + createConstant(index))); +} + +GraphBuilder::Verdict GraphBuilder::startInstruction(Moth::Instr::Type /*instr*/) +{ + // This handles a couple of cases on how flow control can end up at this instruction. + + const auto off = currentInstructionOffset(); + if (auto newEnv = m_envForOffset[off]) { + // Ok, there was a jump from before to this point (which registered an environment), so we + // have two options: + if (env() != newEnv && env() != nullptr) { + // There is a current environment different from the environment active when we took the + // jump. This happens with e.g. an if-then-else: + // + // acc = condition + // JumpFalse else-block + // ... then block + // Jump end-if + // else-block: + // ... else block + // end-if: + // .. some instruction <--- we're here + // + // in that case we merge the after-else environment into the after-then environment: + newEnv->merge(env()); + } else { + // There is not a current environment. This can happen with e.g. a loop: + // loop-start: + // acc = condition + // JumpFalse loop-end + // ... loop body + // Jump loop-start + // loop-end: + // .... some instruction <--- we're here + // + // The last jump of the loop will clear the environment, so at this point we only have + // the environment registered by the JumpFalse. This is the asy case: no merges, just + // take the registered environment unchanged. + } + + // Leave the merged environment as-is, and continue with a copy. We cannot change the + // registered environment in case this point also happens to be a loop start. + setEnv(newEnv->copy()); + } + + if (env() == nullptr) { + // Ok, there is no environment, meaning nobody jumped to this instruction, and the previous + // instruction doesn't let control flow end up here. So, this is dead code. + // This can happen for JS like: + // + // if (condition) { + // return something + // } else { + // return somethingElse + // } + // someCode <--- we're here + return SkipInstruction; + } + + const LabelInfo *info = isLoopStart(off); + if (info && env()) { + // Ok, this instruction is the start of a loop, meaning there will be a jump backwards to + // this point. Make sure there is a Region node with Phi nodes here. + handleLoopStart(*info); + } + + return ProcessInstruction; +} + +void GraphBuilder::endInstruction(Moth::Instr::Type /*instr*/) {} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4graphbuilder_p.h b/src/qml/jit/qv4graphbuilder_p.h new file mode 100644 index 0000000000..6393cab9ef --- /dev/null +++ b/src/qml/jit/qv4graphbuilder_p.h @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4GRAPHBUILDER_P_H +#define QV4GRAPHBUILDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4global_p.h> +#include <private/qv4bytecodehandler_p.h> +#include <private/qv4ir_p.h> +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +// The graph builder walks the byte-code, and produces a graph. The graph is a digraph, where the +// nodes have operations, and the edges are dependencies (or inputs). +class GraphBuilder: protected Moth::ByteCodeHandler +{ + Q_DISABLE_COPY_MOVE(GraphBuilder) + + enum { NoTraceSlot = -1 }; + + struct LabelInfo { //### extend this to also capture the amount of slots that are live + LabelInfo() = default; + LabelInfo(unsigned label) : labelOffset(label) {} + unsigned labelOffset = 0; + }; + +public: + static void buildGraph(IR::Function *function); + + class InterpreterEnvironment; + + void setEnv(InterpreterEnvironment *newEnv) + { m_currentEnv = newEnv; } + + InterpreterEnvironment *env() const + { return m_currentEnv; } + +private: + GraphBuilder(IR::Function *function); + ~GraphBuilder() override = default; + + void startGraph(); + void endGraph(); + + Node *bindAcc(Node *n); + Node *createAndLinkNode(Operation *op, Node *operands[], size_t opCount, bool incomplete = false); + Node *createNode(Operation *op, bool incomplete = false); + Node *createNode(Operation *op, Node *n1); + Node *createNode(Operation *op, Node *n1, Node *n2); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3); + Node *createNode(Operation *op, Node *n1, Node *n2, Node *n3, Node *n4); + Node *createRegion(unsigned nControlInputs); + Node *createIfTrue(); + Node *createIfFalse(); + Node *createConstant(int v); + Node *createPhi(unsigned nInputs, Node *input, Node *control); + Node *createEffectPhi(unsigned nInputs, Node *input, Node *control); + Node *createHandleUnwind(int offset); + Node *mergeControl(Node *c1, Node *c2); + Node *mergeEffect(Node *e1, Node *e2, Node *control); + Node *mergeValue(Node *v1, Node *v2, Node *control); + + Node *createToBoolean(Node *input); + + using VarArgNodes = QVarLengthArray<Node *, 32>; + void populate(VarArgNodes &args, int argc, int argv); + + void queueFunctionExit(Node *exitNode); + + Function *function() const + { return m_func; } + + Graph *graph() + { return m_graph; } + + Node *mergeIntoSuccessor(int offset); + + OperationBuilder *opBuilder() const + { return m_graph->opBuilder(); } + + int absoluteOffset(int offset) const + { return offset + nextInstructionOffset(); } + + const LabelInfo *labelInfoAt(unsigned offset) const; + const LabelInfo *isLoopStart(unsigned offset) const; + void handleLoopStart(const LabelInfo &labelInfo); + void startUnwinding(); + +protected: // ByteCodeHandler + void generate_Ret() override; + void generate_Debug() override; + void generate_LoadConst(int index) override; + void generate_LoadZero() override; + void generate_LoadTrue() override; + void generate_LoadFalse() override; + void generate_LoadNull() override; + void generate_LoadUndefined() override; + void generate_LoadInt(int value) override; + void generate_MoveConst(int constIndex, int destTemp) override; + void generate_LoadReg(int reg) override; + void generate_StoreReg(int reg) override; + void generate_MoveReg(int srcReg, int destReg) override; + void generate_LoadImport(int index) override; + void generate_LoadLocal(int index, int traceSlot) override; + void generate_StoreLocal(int index) override; + void generate_LoadScopedLocal(int scope, int index, int traceSlot) override; + void generate_StoreScopedLocal(int scope, int index) override; + void generate_LoadRuntimeString(int stringId) override; + void generate_MoveRegExp(int regExpId, int destReg) override; + void generate_LoadClosure(int value) override; + void generate_LoadName(int name, int traceSlot) override; + void generate_LoadGlobalLookup(int index, int traceSlot) override; + void generate_StoreNameSloppy(int name) override; + void generate_StoreNameStrict(int name) override; + void generate_LoadElement(int base, int traceSlot) override; + void generate_StoreElement(int base, int index, int traceSlot) override; + void generate_LoadProperty(int name, int traceSlot) override; + void generate_GetLookup(int index, int traceSlot) override; + void generate_StoreProperty(int name, int base) override; + void generate_SetLookup(int index, int base) override; + void generate_LoadSuperProperty(int property) override; + void generate_StoreSuperProperty(int property) override; + void generate_StoreScopeObjectProperty(int base, + int propertyIndex) override; + void generate_StoreContextObjectProperty(int base, + int propertyIndex) override; + void generate_LoadScopeObjectProperty(int propertyIndex, int base, + int captureRequired) override; + void generate_LoadContextObjectProperty(int propertyIndex, int base, + int captureRequired) override; + void generate_LoadIdObject(int index, int base) override; + void generate_Yield() override; + void generate_YieldStar() override; + void generate_Resume(int offset) override; + void finalizeCall(Operation::Kind kind, VarArgNodes &args, int argc, int argv); + void generate_CallValue(int name, int argc, int argv, int traceSlot) override; + void generate_CallWithReceiver(int name, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_CallProperty(int name, int base, int argc, int argv, int traceSlot) override; + void generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv, + int traceSlot) override; + void generate_CallElement(int base, int index, int argc, int argv, int traceSlot) override; + void generate_CallName(int name, int argc, int argv, int traceSlot) override; + void generate_CallPossiblyDirectEval(int argc, int argv, int traceSlot) override; + void generate_CallGlobalLookup(int index, int argc, int argv, int traceSlot) override; + void generate_CallScopeObjectProperty(int propIdx, int base, int argc, int argv, + int traceSlot) override; + void generate_CallContextObjectProperty(int propIdx, int base, int argc, int argv, + int traceSlot) override; + void generate_SetUnwindHandler(int offset) override; + void generate_UnwindDispatch() override; + void generate_UnwindToLabel(int level, int offset) override; + void generate_DeadTemporalZoneCheck(int name) override; + void generate_ThrowException() override; + void generate_GetException() override; + void generate_SetException() override; + void generate_CreateCallContext() override; + void generate_PushCatchContext(int index, int name) override; + void generate_PushWithContext() override; + void generate_PushBlockContext(int index) override; + void generate_CloneBlockContext() override; + void generate_PushScriptContext(int index) override; + void generate_PopScriptContext() override; + void generate_PopContext() override; + void generate_GetIterator(int iterator) override; + void generate_IteratorNextAndFriends_TrailingStuff(Node *iterationNode, int resultSlot); + void generate_IteratorNext(int value, int done) override; + void generate_IteratorNextForYieldStar(int iterator, int object) override; + void generate_IteratorClose(int done) override; + void generate_DestructureRestElement() override; + void generate_DeleteProperty(int base, int index) override; + void generate_DeleteName(int name) override; + void generate_TypeofName(int name) override; + void generate_TypeofValue() override; + void generate_DeclareVar(int varName, int isDeletable) override; + void generate_DefineArray(int argc, int argv) override; + void generate_DefineObjectLiteral(int internalClassId, int argc, int argv) override; + void generate_CreateClass(int classIndex, int heritage, int computedNames) override; + void generate_CreateMappedArgumentsObject() override; + void generate_CreateUnmappedArgumentsObject() override; + void generate_CreateRestParameter(int argIndex) override; + void generate_ConvertThisToObject() override; + void generate_LoadSuperConstructor() override; + void generate_ToObject() override; + void generate_CallWithSpread(int func, int thisObject, int argc, int argv, + int traceSlot) override; + void generate_TailCall(int func, int thisObject, int argc, int argv) override; + void generate_Construct(int func, int argc, int argv) override; + void generate_ConstructWithSpread(int func, int argc, int argv) override; + void generate_Jump(int offset) override; + void generate_JumpTrue(int traceSlot, int offset) override; + void generate_JumpFalse(int traceSlot, int offset) override; + void generate_JumpFalse(Node *condition, int traceSlot, int offset); + void generate_JumpNoException(int offset) override; + void generate_JumpNotUndefined(int offset) override; + void generate_CmpEqNull() override; + void generate_CmpNeNull() override; + void generate_CmpEqInt(int lhs) override; + void generate_CmpNeInt(int lhs) override; + void generate_CmpEq(int lhs) override; + void generate_CmpNe(int lhs) override; + void generate_CmpGt(int lhs) override; + void generate_CmpGe(int lhs) override; + void generate_CmpLt(int lhs) override; + void generate_CmpLe(int lhs) override; + void generate_CmpStrictEqual(int lhs) override; + void generate_CmpStrictNotEqual(int lhs) override; + void generate_CmpIn(int lhs) override; + void generate_CmpInstanceOf(int lhs) override; + void generate_UNot() override; + void generate_UPlus() override; + void generate_UMinus(int traceSlot) override; + void generate_UCompl() override; + void generate_Increment(int traceSlot) override; + void generate_Decrement(int traceSlot) override; + void generate_Add(int lhs, int traceSlot) override; + void generate_BitAnd(int lhs) override; + void generate_BitOr(int lhs) override; + void generate_BitXor(int lhs) override; + void generate_UShr(int lhs) override; + void generate_Shr(int lhs) override; + void generate_Shl(int lhs) override; + void generate_BitAndConst(int rhs) override; + void generate_BitOrConst(int rhs) override; + void generate_BitXorConst(int rhs) override; + void generate_UShrConst(int rhs) override; + void generate_ShrConst(int rhs) override; + void generate_ShlConst(int rhs) override; + void generate_Exp(int lhs) override; + void generate_Mul(int lhs, int traceSlot) override; + void generate_Div(int lhs) override; + void generate_Mod(int lhs, int traceSlot) override; + void generate_Sub(int lhs, int traceSlot) override; + void generate_LoadQmlContext(int result) override; + void generate_LoadQmlImportedScripts(int result) override; + void generate_InitializeBlockDeadTemporalZone(int firstReg, int count) override; + void generate_ThrowOnNullOrUndefined() override; + void generate_GetTemplateObject(int index) override; + + Verdict startInstruction(Moth::Instr::Type instr) override; + void endInstruction(Moth::Instr::Type instr) override; + +private: + IR::Function *m_func; + Graph *m_graph; + InterpreterEnvironment *m_currentEnv; + std::vector<Node *> m_exitControls; + QHash<int, InterpreterEnvironment *> m_envForOffset; + std::vector<LabelInfo> m_labelInfos; + int m_currentUnwindHandlerOffset = 0; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4GRAPHBUILDER_P_H diff --git a/src/qml/jit/qv4ir.cpp b/src/qml/jit/qv4ir.cpp new file mode 100644 index 0000000000..0b82330394 --- /dev/null +++ b/src/qml/jit/qv4ir.cpp @@ -0,0 +1,382 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qqmlglobal_p.h> +#include "qv4ir_p.h" +#include "qv4node_p.h" +#include "qv4function_p.h" +#include <qv4graph_p.h> +#include "qv4stackframe_p.h" +#include "qv4operation_p.h" +#include "qv4util_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qfile.h> + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Q_LOGGING_CATEGORY(lcJsonIR, "qt.v4.ir.json"); +Q_LOGGING_CATEGORY(lcDotIR, "qt.v4.ir.dot"); +Q_LOGGING_CATEGORY(lcVerify, "qt.v4.ir.verify"); + +Function::Function(QV4::Function *qv4Function) + : qv4Function(qv4Function) + , m_graph(Graph::create(this)) + , m_dumper(nullptr) + , m_nodeInfo(128, nullptr) +{ +} + +Function::~Function() +{ + delete m_dumper; +} + +QString Function::name() const +{ + QString name; + if (auto n = v4Function()->name()) + name = n->toQString(); + if (name.isEmpty()) + name.sprintf("%p", static_cast<void *>(v4Function())); + auto loc = v4Function()->sourceLocation(); + return name + QStringLiteral(" (%1:%2:%3)").arg(loc.sourceFile, QString::number(loc.line), + QString::number(loc.column)); +} + +void Function::dump(const QString &description) const +{ + Dumper::dump(this, description); +} + +void Function::dump() const +{ + dump(QStringLiteral("Debug:")); +} + +Dumper *Function::dumper() const +{ + if (!m_dumper) + m_dumper = new Dumper(this); + return m_dumper; +} + +Function::StringId Function::addString(const QString &s) +{ + m_stringPool.push_back(s); + return m_stringPool.size() - 1; +} + +NodeInfo *Function::nodeInfo(Node *n, bool createIfNecessary) const +{ + if (n->id() >= m_nodeInfo.size()) + m_nodeInfo.resize(n->id() * 2, nullptr); + + NodeInfo *&info = m_nodeInfo[n->id()]; + if (info == nullptr && createIfNecessary) { + info = m_pool.New<NodeInfo>(); + info->setType(n->operation()->type()); + } + return info; +} + +void Function::copyBytecodeOffsets(Node *from, Node *to) +{ + auto toInfo = nodeInfo(to); + if (auto fromInfo = nodeInfo(from)) { + toInfo->setBytecodeOffsets(fromInfo->currentInstructionOffset(), + fromInfo->nextInstructionOffset()); + } +} + +Dumper::Dumper(const Function *f) +{ + if (!f) + return; +} + +void Dumper::dump(const Function *f, const QString &description) +{ + if (false && lcJsonIR().isDebugEnabled()) { + Dumper *dumper = f->dumper(); + + qCDebug(lcJsonIR).noquote().nospace() << description + QLatin1String(":\n"); + for (const auto &line : dumper->dump(f).split('\n')) + qCDebug(lcJsonIR).noquote().nospace() << line; + } + + if (lcDotIR().isDebugEnabled()) + dot(f, description); +} + +QByteArray Dumper::dump(const Function *f) +{ + QJsonObject fo; + + { + QString name; + if (auto n = f->v4Function()->name()) + name = n->toQString(); + fo[QLatin1String("_searchKey")] = QStringLiteral("function %1").arg(name); + if (name.isEmpty()) + name.sprintf("%p", static_cast<void *>(f->v4Function())); + fo[QLatin1String("name")] = name; + } + + auto loc = f->v4Function()->sourceLocation(); + fo[QLatin1String("source")] = loc.sourceFile; + fo[QLatin1String("line")] = loc.line; + fo[QLatin1String("column")] = loc.column; + + { + QJsonArray gn; + QJsonArray ge; + NodeCollector nodes(f->graph(), /*collectUses =*/ true); + nodes.sortById(); + for (Node *n : nodes.reachable()) { + gn.append(dump(n, f)); + int inputIndex = 0; + for (Node *input : n->inputs()) { + QJsonObject edge; + edge[QLatin1String("from")] = int(input->id()); + edge[QLatin1String("to")] = int(n->id()); + edge[QLatin1String("index")] = inputIndex; + if (inputIndex < n->operation()->valueInputCount()) { + edge[QLatin1String("type")] = QLatin1String("value"); + } else if (inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount()) { + edge[QLatin1String("type")] = QLatin1String("effect"); + } else { + edge[QLatin1String("type")] = QLatin1String("control"); + } + Q_ASSERT(inputIndex < n->operation()->valueInputCount() + + n->operation()->effectInputCount() + + n->operation()->controlInputCount()); + ge.append(edge); + ++inputIndex; + } + } + QJsonObject g; + g[QLatin1String("nodes")] = gn; + g[QLatin1String("edges")] = ge; + fo[QLatin1String("graph")] = g; + } + + m_doc.setObject(fo); + return m_doc.toJson(QJsonDocument::Indented); +} + +QJsonValue toJSonValue(QV4::Value v) +{ + switch (v.type()) { + case QV4::Value::Undefined_Type: return QJsonValue(QJsonValue::Undefined); + case QV4::Value::Null_Type: return QJsonValue(QJsonValue::Null); + case QV4::Value::Boolean_Type: return QJsonValue(v.booleanValue()); + case QV4::Value::Integer_Type: return QJsonValue(v.int_32()); + case QV4::Value::Managed_Type: + if (String *s = v.stringValue()) + return QJsonValue(s->toQString()); + else + return QJsonValue(QLatin1String("<managed>")); + default: return QJsonValue(v.doubleValue()); + } +} + +QJsonValue Dumper::dump(const Node * const node, const Function *f) +{ + QJsonObject n; + n[QLatin1String("id")] = int(node->id()); + n[QLatin1String("kind")] = node->operation()->debugString(); + switch (node->operation()->kind()) { + case Meta::Parameter: { + auto info = ParameterPayload::get(*node->operation()); + n[QLatin1String("name")] = f->string(info->stringId()); + n[QLatin1String("index")] = int(info->parameterIndex()); + break; + } + case Meta::Constant: { + auto info = ConstantPayload::get(*node->operation()); + n[QLatin1String("value")] = toJSonValue(info->value()); + break; + } + default: + break; + } + return n; +} + +void Dumper::dot(const Function *f, const QString &description) +{ + static const bool skipFramestate = qEnvironmentVariableIsSet("QV4_JIT_DOT_SKIP_FRAMESTATE"); + + auto node = [](Node *n) { + return QStringLiteral("n%1[label=\"%1: %2%3\"];\n").arg(QString::number(n->id()), + n->operation()->debugString(), + n->isDead() ? QStringLiteral(" (dead)") + : QString()); + }; + + Graph *g = f->graph(); + QString out; + out += QLatin1Char('\n'); + out += QStringLiteral("digraph{root=\"n%1\" label=\"%2\";" + "node[shape=rect];" + "edge[dir=back fontsize=10];\n") + .arg(g->startNode()->id()) + .arg(description); + out += node(g->startNode()); + const bool dumpUses = false; // set to true to see all nodes + NodeCollector nodes(g, dumpUses, skipFramestate); + for (Node *n : nodes.reachable()) { + if (n == g->startNode()) + continue; + + out += node(n); + + unsigned inputIndex = 0; + for (Node *input : n->inputs()) { + if (input == nullptr) + continue; + out += QStringLiteral("n%2->n%1[style=").arg(QString::number(n->id()), + QString::number(input->id())); + if (inputIndex < n->operation()->valueInputCount() || + inputIndex == n->operation()->indexOfFrameStateInput()) { + out += QStringLiteral("solid headlabel=\"%1\"").arg(inputIndex); + } else if (inputIndex < unsigned(n->operation()->valueInputCount() + + n->operation()->effectInputCount())) { + out += QStringLiteral("dotted headlabel=\"%1\"").arg(inputIndex); + } else { + out += QStringLiteral("dashed headlabel=\"%1\"").arg(inputIndex); + } + out += QStringLiteral("];\n"); + ++inputIndex; + } + } + out += QStringLiteral("}\n"); + qCDebug(lcDotIR).nospace().noquote() << out; + + QFile of(description + QStringLiteral(".dot")); + of.open(QIODevice::WriteOnly); + of.write(out.toUtf8()); + of.close(); +} + +void Function::verify() const +{ +#ifndef QT_NO_DEBUG + unsigned problemsFound = 0; + + auto verifyNodeAgainstOperation = [&problemsFound](const Node *n) { + const Operation *op = n->operation(); + if (op->totalInputCount() != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Node" << n->id() << "has" << n->inputCount() + << "inputs, but it's operation" << op->debugString() + << "requires" << op->totalInputCount() << "inputs"; + } + + if (n->opcode() == Meta::Phi || n->opcode() == Meta::EffectPhi) { + if (n->controlInput()->opcode() != Meta::Region) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() << "is not a region"; + } + if (n->controlInput()->inputCount() + 1 != n->inputCount()) { + ++problemsFound; + qCDebug(lcVerify()) << "Control input of phi node" << n->id() + << "has" << n->controlInput()->inputCount() + << "inputs while phi node has" << n->inputCount() + << "inputs"; + } + } + + //### todo: verify outputs: value outputs are allowed to be unused, but the effect and + // control outputs have to be linked up, except: + //### todo: verify if no use is a nullptr, except for operations that can throw, where the + // last one is allowed to be a nullptr when an unwind handler is missing. + }; + + NodeWorkList todo(graph()); + todo.enqueue(graph()->endNode()); + while (Node *n = todo.dequeueNextNodeForVisiting()) { + todo.enqueueAllInputs(n); + todo.enqueueAllUses(n); + + verifyNodeAgainstOperation(n); + } + //### TODO: + if (problemsFound != 0) { + dump(QStringLiteral("Problematic graph")); + qFatal("Found %u problems during graph verification!", problemsFound); + } +#endif // QT_NO_xDEBUG +} + +QString Type::debugString() const +{ + if (isNone()) + return QStringLiteral("none"); + if (isInvalid()) + return QStringLiteral("invalid"); + + QStringList s; + if (m_t & Bool) + s += QStringLiteral("boolean"); + if (m_t & Int32) + s += QStringLiteral("int32"); + if (m_t & Double) + s += QStringLiteral("double"); + if (m_t & Undefined) + s += QStringLiteral("undefined"); + if (m_t & Null) + s += QStringLiteral("null"); + if (m_t & Empty) + s += QStringLiteral("empty"); + if (m_t & RawPointer) + s += QStringLiteral("raw pointer"); + + return s.join(QLatin1String(" ")); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4ir_p.h b/src/qml/jit/qv4ir_p.h new file mode 100644 index 0000000000..e21a80528d --- /dev/null +++ b/src/qml/jit/qv4ir_p.h @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4IR_P_H +#define QV4IR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4function_p.h> +#include <QtCore/qjsondocument.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Dumper; +class Graph; + +class Node; +class NodeInfo; + +class Function +{ + Q_DISABLE_COPY_MOVE(Function) +public: + Function(QV4::Function *qv4Function); + ~Function(); + + void verify() const; + + QV4::Function *v4Function() const + { return qv4Function; } + + QString name() const; + + QQmlJS::MemoryPool *pool() + { return &m_pool; } + + Graph *graph() const + { return m_graph; } + + void dump(const QString &description) const; + void dump() const; // for calling in the debugger + Dumper *dumper() const; + + using StringId = size_t; + StringId addString(const QString &s); + QString string(StringId id) const + { return m_stringPool[id]; } + + NodeInfo *nodeInfo(Node *n, bool createIfNecessary = true) const; + void copyBytecodeOffsets(Node *from, Node *to); + + void addUnwindLabelOffset(int absoluteOffset) + { m_unwindLabelOffsets.push_back(absoluteOffset); } + + const std::vector<int> &unwindLabelOffsets() const + { return m_unwindLabelOffsets; } + +private: + QV4::Function *qv4Function; + mutable QQmlJS::MemoryPool m_pool; + Graph *m_graph; + mutable Dumper *m_dumper; + std::vector<QString> m_stringPool; + mutable std::vector<NodeInfo *> m_nodeInfo; //### move the into the _pool + std::vector<int> m_unwindLabelOffsets; +}; + +class Dumper +{ + Q_DISABLE_COPY_MOVE(Dumper) + +public: + Dumper(const Function *f); + ~Dumper() = default; + + static void dump(const Function *f, const QString &description); + static void dot(const Function *f, const QString &description); + +private: + QByteArray dump(const Function *f); + QJsonValue dump(const Node *node, const Function *f); + +private: + QJsonDocument m_doc; +}; + +class Type +{ + // None is for nodes with no type (e.g. a Return) + // The others form a lattice: + // Any -> Object -> Invalid + // ^^^ -> Number -> Integral -> Int32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> UInt32 -> ^^^^^^^ + // ^^^ -> Number -> Integral -> Bool -> ^^^^^^^ + // ^^^ -> Number -> Double -> ^^^^^^^ + // ^^^ -> Undefined -> ^^^^^^^ + // ^^^ -> Null -> ^^^^^^^ + // ^^^ -> Empty -> ^^^^^^^ + enum InternalType: int16_t { + None = 0, + + Object = 1 << 0, + Bool = 1 << 1, + Int32 = 1 << 2, + UInt32 = 1 << 3, + Double = 1 << 4, + Undefined = 1 << 5, + Null = 1 << 6, + Empty = 1 << 7, + RawPointer = 1 << 8, + Invalid = -1, + + Integral = Int32 | UInt32 | Bool, + Number = Integral | Double, + Any = Object | Number | Undefined | Empty | Null, + }; + + Type(InternalType t) : m_t(t) {} + +public: + Type() = default; + + bool operator==(const Type &other) const + { return m_t == other.m_t; } + + static Type noneType() { return Type(None); } + static Type anyType() { return Type(Any); } + static Type undefinedType() { return Type(Undefined); } + static Type emptyType() { return Type(Empty); } + static Type booleanType() { return Type(Bool); } + static Type int32Type() { return Type(Int32); } + static Type doubleType() { return Type(Double); } + static Type numberType() { return Type(Number); } + static Type nullType() { return Type(Null); } + static Type objectType() { return Type(Object); } + static Type rawPointerType() { return Type(RawPointer); } + + bool isAny() const { return m_t == Any; } + bool isBoolean() const { return m_t == Bool; } + bool isInt32() const { return m_t == Int32; } + bool isInvalid() const { return m_t == Invalid; } + bool isNone() const { return m_t == None; } + bool isDouble() const { return m_t == Double; } + bool isUndefined() const { return m_t == Undefined; } + bool isNull() const { return m_t == Null; } + bool isEmpty() const { return m_t == Empty; } + bool isObject() const { return m_t == Object; } + bool isRawPointer() const { return m_t == RawPointer; } + bool isIntegral() const { return matches(Integral); } + bool isNumber() const { return matches(Number); } + + Type operator|(Type other) const + { return Type(InternalType(int16_t(m_t) | int16_t(other.m_t))); } + + Type &operator|=(Type other) + { + m_t = (InternalType(int16_t(m_t) | int16_t(other.m_t))); + return *this; + } + + QString debugString() const; + +private: + bool matches(InternalType it) const + { + return (m_t & ~it) == 0 && (m_t & it) != 0; + } + +private: + InternalType m_t = None; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4IR_P_H diff --git a/src/qml/jit/qv4node.cpp b/src/qml/jit/qv4node.cpp new file mode 100644 index 0000000000..e059e9fef6 --- /dev/null +++ b/src/qml/jit/qv4node.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4node_p.h" +#include "qv4graph_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +Node *Node::create(Node::MemoryPool *pool, Node::Id id, const Operation *op, size_t nInputs, + Node *const *inputs, bool inputsAreExtensible) +{ + size_t capacity = nInputs; + if (inputsAreExtensible) + capacity += 3; + + Node *node = new (pool->allocate(sizeof(Node))) Node(pool, id, op, unsigned(nInputs), + int(capacity)); + for (uint i = 0; i < capacity; ++i) + new (&node->m_inputs[int(i)]) Use(node); + for (size_t i = 0; i < nInputs; ++i) { + Q_ASSERT(inputs[i] != nullptr); + node->replaceInput(unsigned(i), inputs[i]); + } + + return node; +} + +void Node::addInput(MemoryPool *pool, Node *in) +{ + Q_ASSERT(in); + ++m_nInputs; + if (m_nInputs >= unsigned(m_inputs.size())) { + QQmlJS::FixedPoolArray<Use> oldInputs = m_inputs; + m_inputs = QQmlJS::FixedPoolArray<Use>(pool, int(m_nInputs + 3)); + for (Use &input : m_inputs) + new (&input) Use(this); + for (int i = 0, ei = oldInputs.size(); i != ei; ++i) { + Node *in = oldInputs[i].m_input; + oldInputs[i].set(nullptr); + m_inputs[i].set(in); + } + } + m_inputs.at(int(m_nInputs - 1)).set(in); +} + +void Node::removeInput(unsigned index) +{ + Q_ASSERT(index < inputCount()); + for (unsigned i = index, ei = inputCount(); i < ei - 1; ++i) + replaceInput(i, input(i + 1)); + trimInputCount(inputCount() - 1); +} + +void Node::removeInputs(unsigned start, unsigned count) +{ + for (unsigned idx = start; idx < start + count; ++idx) + m_inputs.at(int(idx)).set(nullptr); +} + +void Node::removeAllInputs() +{ + removeInputs(0, inputCount()); +} + +void Node::trimInputCount(unsigned newCount) +{ + unsigned currentCount = inputCount(); + if (newCount == currentCount) + return; + Q_ASSERT(newCount < currentCount); + removeInputs(newCount, currentCount - newCount); + m_nInputs = newCount; +} + +void Node::removeExceptionHandlerUse() +{ + for (Use* use = m_firstUse; use; use = use->m_next) { + if (use->m_input->opcode() == Meta::OnException) { + use->set(nullptr); + break; + } + } +} + +void Node::insertInput(Node::MemoryPool *pool, unsigned index, Node *newInput) +{ + Q_ASSERT(index < inputCount()); + addInput(pool, input(inputCount() - 1)); + for (unsigned i = inputCount() - 1; i > index; --i) + replaceInput(i, input(i - 1)); + replaceInput(index, newInput); +} + +void Node::replaceAllUsesWith(Node *replacement) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const unsigned inIdx = use->inputIndex(); + use->user()->replaceInput(inIdx, replacement); + use = next; + } +} + +void Node::replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput) +{ + for (Use *use = m_firstUse; use; ) { + Use *next = use->m_next; + const Operation *inOp = use->user()->operation(); + const unsigned inIdx = use->inputIndex(); + if (inIdx < inOp->valueInputCount()) + use->user()->replaceInput(inIdx, newValueInput); + else if (inIdx < inOp->indexOfFirstControl()) + use->user()->replaceInput(inIdx, newEffectInput); + else + use->user()->replaceInput(inIdx, newControlInput); + use = next; + } +} + +Node *Node::firstValueUse() +{ + for (auto it = uses().begin(), eit = uses().end(); it != eit; ++it) { + if (it.isUsedAsValue()) + return *it; + } + return nullptr; +} + +Node::Node(MemoryPool *pool, Node::Id id, const Operation *op, unsigned nInputs, int capacity) + : m_op(op) + , m_inputs(pool, capacity) + , m_nInputs(nInputs) + , m_id(id) +{ +} + +NodeWorkList::NodeWorkList(const Graph *g) + : m_nodeState(g->nodeCount(), Unvisited) +{ m_worklist.reserve(64); } + +void NodeWorkList::reset() +{ + std::fill(m_nodeState.begin(), m_nodeState.end(), Unvisited); + m_worklist.clear(); + if (m_worklist.capacity() < 64) + m_worklist.reserve(64); +} + +NodeCollector::NodeCollector(const Graph *g, bool collectUses, bool skipFramestate) +{ + markReachable(g->endNode()); + for (size_t i = 0; i < m_reachable.size(); ++i) { // _reachable.size() is on purpose! + Node *n = m_reachable.at(i); + for (auto input : n->inputs()) { + if (input == nullptr) + continue; + if (isReachable(input->id())) + continue; + if (skipFramestate && input->opcode() == Meta::FrameState) + continue; + markReachable(input); + } + + if (collectUses) { + for (Node *use : n->uses()) { + if (use && !isReachable(use->id())) + markReachable(use); + } + } + } +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4node_p.h b/src/qml/jit/qv4node_p.h new file mode 100644 index 0000000000..679a29764a --- /dev/null +++ b/src/qml/jit/qv4node_p.h @@ -0,0 +1,642 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4NODE_P_H +#define QV4NODE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmljsmemorypool_p.h> +#include <private/qv4global_p.h> +#include <private/qv4operation_p.h> +#include "qv4util_p.h" + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +class Use +{ + Q_DISABLE_COPY_MOVE(Use) + +public: + Use(Node *user) + : m_user(user) + {} + + ~Use() + { + if (m_input) + removeFromList(); + } + + operator Node *() const { return m_input; } + Node *input() const { return m_input; } + Node *user() const { return m_user; } + + inline void set(Node *newInput); + + void validate() const + { + Q_ASSERT(m_user); + if (m_input) { + if (m_prev != nullptr) + Q_ASSERT(*m_prev == this); + if (m_next) { + Q_ASSERT(m_next->m_input == m_input); + Q_ASSERT(m_next->m_prev == &m_next); + m_next->validate(); + } + } + } + + inline int inputIndex() const; + +protected: + friend class Node; + + void addToList(Use **list) { + validate(); + m_next = *list; + if (m_next) + m_next->m_prev = &m_next; + m_prev = list; + *list = this; + validate(); + } + + void removeFromList() { + validate(); + Use **newPrev = m_prev; + *newPrev = m_next; + m_prev = nullptr; + if (m_next) + m_next->m_prev = newPrev; + m_next = nullptr; + m_input = nullptr; + validate(); + } + +private: + Node *m_input = nullptr; + Node *m_user = nullptr; + Use *m_next = nullptr; + Use **m_prev = nullptr; +}; + +// A node represents an calculation, action, or marker in the graph. Each node has an operation, +// input dependencies and uses. The operation indicates what kind of node it is, e.g.: JSAdd, +// Constant, Region, and so on. Two nodes can have the same operation, but different inputs. +// For example, the expressions 1 + 2 and 3 + 4 will each have a node with an JSAdd operation +// (which is exactly the same operation for both nodes), but the nodes have different inputs (1, and +// 2 in the first expression, while the second operation has 3 and 4 as inputs). +class Node final +{ + Q_DISABLE_COPY_MOVE(Node) + +public: + using Id = uint32_t; + using MemoryPool = QQmlJS::MemoryPool; + class Inputs; + +public: + static Node *create(MemoryPool *pool, Id id, const Operation *op, size_t nInputs, + Node * const *inputs, bool inputsAreExtensible = false); + ~Node() = delete; + + inline bool isDead() const; + inline void kill(); + + Id id() const { return m_id; } + + const Operation *operation() const + { return m_op; } + + void setOperation(const Operation *op) + { m_op = op; } + + Operation::Kind opcode() const + { return operation()->kind(); } + + inline Inputs inputs() const; + void addInput(MemoryPool *pool, Node *in); + void removeInput(unsigned index); + void removeInputs(unsigned start, unsigned count); + void removeAllInputs(); + uint32_t inputCount() const + { return m_nInputs; } + void trimInputCount(unsigned newCount); + + void removeExceptionHandlerUse(); + + Node *input(unsigned idx) const + { + Q_ASSERT(idx < inputCount()); + return m_inputs.at(idx); + } + + Node *effectInput(unsigned effectIndex = 0) const + { + if (operation()->effectInputCount() == 0) + return nullptr; + Q_ASSERT(effectIndex < operation()->effectInputCount()); + return input(operation()->indexOfFirstEffect() + effectIndex); + } + + Node *controlInput(unsigned controlIndex = 0) const + { + if (operation()->controlInputCount() == 0) + return nullptr; + Q_ASSERT(controlIndex < operation()->controlInputCount()); + return input(operation()->indexOfFirstControl() + controlIndex); + } + + Node *frameStateInput() const + { + if (operation()->hasFrameStateInput()) + return input(operation()->indexOfFrameStateInput()); + return nullptr; + } + + void setFrameStateInput(Node *newFramestate) + { + if (operation()->hasFrameStateInput()) + replaceInput(operation()->indexOfFrameStateInput(), newFramestate); + } + + void insertInput(MemoryPool *pool, unsigned index, Node *newInput); + + void replaceInput(Node *oldIn, Node *newIn) + { + for (unsigned i = 0, ei = inputCount(); i != ei; ++i) { + if (input(i) == oldIn) + replaceInput(i, newIn); + } + } + + void replaceInput(unsigned idx, Node *newIn) + { + m_inputs[idx].set(newIn); + } + + class Uses + { + public: + explicit Uses(Node *node) + : m_node(node) + {} + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool isEmpty() const; + + private: + Node *m_node; + }; + + Uses uses() { return Uses(this); } + bool hasUses() const { return m_firstUse != nullptr; } + unsigned useCount() const + { + unsigned cnt = 0; + for (Use *it = m_firstUse; it; it = it->m_next) + ++cnt; + return cnt; + } + void replaceAllUsesWith(Node *replacement); + void replaceUses(Node *newValueInput, Node *newEffectInput, Node *newControlInput); + + Node *firstValueUse(); + +private: // types and utility methods + friend class Use; + Node(MemoryPool *pool, Id id, const Operation *op, unsigned nInputs, int capacity); + +private: // fields + Use *m_firstUse = nullptr; + const Operation *m_op = nullptr; + QQmlJS::FixedPoolArray<Use> m_inputs; + unsigned m_nInputs = 0; + Id m_id = 0; +}; + +void Use::set(Node *newInput) +{ + if (m_input) + removeFromList(); + m_input = newInput; + if (newInput) + addToList(&newInput->m_firstUse); +} + +class Node::Inputs final +{ +public: + using value_type = Node *; + + class const_iterator; + inline const_iterator begin() const; + inline const_iterator end() const; + + bool empty() const + { return m_nInputs == 0; } + + unsigned count() const + { return m_nInputs; } + + explicit Inputs(const Use *inputs, unsigned nInputs) + : m_inputs(inputs), m_nInputs(nInputs) + {} + +private: + const Use *m_inputs = nullptr; + unsigned m_nInputs = 0; +}; + +class Node::Inputs::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Node *; + using pointer = const value_type *; + using reference = value_type &; + + Node *operator*() const + { return m_inputs->m_input; } + + bool operator==(const const_iterator &other) const + { return m_inputs == other.m_inputs; } + + bool operator!=(const const_iterator &other) const + { return !(*this == other); } + + const_iterator &operator++() + { ++m_inputs; return *this; } + + const_iterator& operator+=(difference_type offset) + { m_inputs += offset; return *this; } + + const_iterator operator+(difference_type offset) const + { return const_iterator(m_inputs + offset); } + + difference_type operator-(const const_iterator &other) const + { return m_inputs - other.m_inputs; } + +private: + friend class Node::Inputs; + + explicit const_iterator(const Use *inputs) + : m_inputs(inputs) + {} + + const Use *m_inputs; +}; + +Node::Inputs::const_iterator Node::Inputs::begin() const +{ return const_iterator(m_inputs); } + +Node::Inputs::const_iterator Node::Inputs::end() const +{ return const_iterator(m_inputs + m_nInputs); } + +Node::Inputs Node::inputs() const +{ + return Inputs(m_inputs.begin(), m_nInputs); +} + +class Node::Uses::const_iterator final +{ +public: + using iterator_category = std::forward_iterator_tag; + using difference_type = int; + using value_type = Node *; + using pointer = Node **; + using reference = Node *&; + + Node *operator*() const + { return m_current->user(); } + + bool operator==(const const_iterator &other) const + { return other.m_current == m_current; } + + bool operator!=(const const_iterator &other) const + { return other.m_current != m_current; } + + const_iterator &operator++() + { m_current = m_next; setNext(); return *this; } + + unsigned inputIndex() const + { return m_current->inputIndex(); } + + bool isUsedAsValue() const + { return inputIndex() < operator*()->operation()->valueInputCount(); } + + bool isUsedAsControl() const + { return operator*()->operation()->indexOfFirstControl() <= inputIndex(); } + +private: + friend class Node::Uses; + + const_iterator() = default; + + explicit const_iterator(Node* node) + : m_current(node->m_firstUse) + { setNext(); } + + void setNext() + { + if (m_current) + m_next = m_current->m_next; + else + m_next = nullptr; + } + +private: + Use *m_current = nullptr; + Use *m_next = nullptr; +}; + +Node::Uses::const_iterator Node::Uses::begin() const +{ return const_iterator(this->m_node); } + +Node::Uses::const_iterator Node::Uses::end() const +{ return const_iterator(); } + +int Use::inputIndex() const +{ + if (!m_user) + return -1; + return int(this - m_user->m_inputs.begin()); +} + +bool Node::isDead() const +{ + Inputs in = inputs(); + return !in.empty() && *in.begin() == nullptr; +} + +void Node::kill() +{ + removeAllInputs(); +} + +class NodeWorkList final +{ + enum State: uint8_t { + Unvisited = 0, + Queued, + Visited, + }; + +public: + NodeWorkList(const Graph *g); + + void reset(); + + bool enqueue(Node *n) + { + State &s = nodeState(n); + if (s == Queued || s == Visited) + return false; + + m_worklist.push_back(n); + s = Queued; + return true; + } + + void enqueue(const std::vector<Node *> &nodes) + { + m_worklist.insert(m_worklist.end(), nodes.begin(), nodes.end()); + for (Node *n : nodes) + nodeState(n) = Queued; + } + + void reEnqueue(Node *n) + { + if (!n) + return; + State &s = nodeState(n); + if (s == Queued) + return; + s = Queued; + m_worklist.push_back(n); + } + + void reEnqueueLate(Node *n) + { + if (!n) + return; + State &s = nodeState(n); + if (s == Queued) + return; + s = Queued; + m_worklist.insert(m_worklist.begin(), n); + } + + void enqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + enqueue(input); + } + + void reEnqueueAllInputs(Node *n) + { + for (Node *input : n->inputs()) + reEnqueue(input); + } + + void enqueueValueInputs(Node *n) + { + for (unsigned i = 0, ei = n->operation()->valueInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueEffectInputs(Node *n) + { + for (unsigned i = n->operation()->indexOfFirstEffect(), ei = n->operation()->effectInputCount(); i != ei; ++i) + enqueue(n->input(i)); + } + + void enqueueAllUses(Node *n) + { + for (Node *use : n->uses()) + enqueue(use); + } + + Node *dequeueNextNodeForVisiting() + { + while (!m_worklist.empty()) { + Node *n = m_worklist.back(); + m_worklist.pop_back(); + State &s = nodeState(n); + if (s == Queued) { + s = Visited; + return n; + } + Q_UNREACHABLE(); + } + + return nullptr; + } + + void markAsVisited(Node *n) + { nodeState(n) = Visited; } + + bool isVisited(Node *n) const + { return nodeState(n) == Visited; } + + bool isEmpty() const + { return m_worklist.empty(); } + + QString status(Node *n) const + { + QString s = QStringLiteral("status for node %1: ").arg(n->id()); + switch (nodeState(n)) { + case Queued: s += QLatin1String("queued"); break; + case Visited: s += QLatin1String("visited"); break; + case Unvisited: s += QLatin1String("unvisited"); break; + } + return s; + } + +private: + State &nodeState(Node *n) + { + const unsigned position(n->id()); + if (position >= m_nodeState.size()) + m_nodeState.resize(position + 1, Unvisited); + + return m_nodeState[position]; + } + + State nodeState(Node *n) const + { return m_nodeState[unsigned(n->id())]; } + +private: + std::vector<Node *> m_worklist; + std::vector<State> m_nodeState; +}; + +class NodeInfo +{ +public: + enum { NoInstructionOffset = -1 }; + +public: + NodeInfo() = default; + + Type type() const { return m_type; } + void setType(Type t) { m_type = t; } + + int currentInstructionOffset() const + { return m_currentInstructionOffset; } + + int nextInstructionOffset() const + { return m_nextInstructionOffset; } + + void setBytecodeOffsets(int current, int next) + { + Q_ASSERT(current != NoInstructionOffset); + Q_ASSERT(next != NoInstructionOffset); + m_currentInstructionOffset = current; + m_nextInstructionOffset = next; + } + +private: + Type m_type; + int m_currentInstructionOffset = NoInstructionOffset; + int m_nextInstructionOffset = NoInstructionOffset; +}; + +class NodeCollector +{ +public: + NodeCollector(const Graph *g, bool collectUses = false, bool skipFramestate = false); + + const std::vector<Node *> &reachable() const + { return m_reachable; } + + void sortById() + { + std::sort(m_reachable.begin(), m_reachable.end(), [](Node *n1, Node *n2) { + return n1->id() < n2->id(); + }); + } + + bool isReachable(Node::Id nodeId) const + { + if (nodeId >= Node::Id(m_isReachable.size())) + return false; + return m_isReachable.at(int(nodeId)); + } + + void markReachable(Node *node) + { + auto nodeId = node->id(); + m_reachable.push_back(node); + if (nodeId >= Node::Id(m_isReachable.size())) + m_isReachable.resize(int(nodeId + 1), false); + m_isReachable.setBit(int(nodeId)); + } + +private: + std::vector<Node *> m_reachable; + BitVector m_isReachable; +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4NODE_P_H diff --git a/src/qml/jit/qv4operation.cpp b/src/qml/jit/qv4operation.cpp new file mode 100644 index 0000000000..8356a35098 --- /dev/null +++ b/src/qml/jit/qv4operation.cpp @@ -0,0 +1,782 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4operation_p.h" +#include "qv4runtimesupport_p.h" + +QT_BEGIN_NAMESPACE +namespace QV4 { +namespace IR { + +OperationBuilder::OperationBuilder(QQmlJS::MemoryPool *graphPool) + : m_graphPool(graphPool) +{} + +OperationBuilder *OperationBuilder::create(QQmlJS::MemoryPool *pool) +{ + return pool->New<OperationBuilder>(pool); +} + +Operation *OperationBuilder::getConstant(Value v) +{ + Type t; + switch (v.type()) { + case Value::Undefined_Type: t = Type::undefinedType(); break; + case Value::Integer_Type: t = Type::int32Type(); break; + case Value::Boolean_Type: t = Type::booleanType(); break; + case Value::Null_Type: t = Type::nullType(); break; + case Value::Double_Type: t = Type::doubleType(); break; + case Value::Managed_Type: t = Type::objectType(); break; + default: + if (v.isEmpty()) + t = Type::emptyType(); + else + Q_UNREACHABLE(); + } + return OperationWithPayload<ConstantPayload>::create( + m_graphPool, Meta::Constant, 0, 0, 0, 1, 0, 0, t, Operation::NoFlags, + ConstantPayload(v)); +} + +Operation *OperationBuilder::getParam(unsigned index, const Function::StringId name) +{ + return OperationWithPayload<ParameterPayload>::create(m_graphPool, Meta::Parameter, + 1, 0, 0, + 1, 0, 0, + Type::anyType(), Operation::NoFlags, + ParameterPayload(index, name)); +} + +Operation *OperationBuilder::getRegion(unsigned nControlInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Region, + 0, 0, uint16_t(nControlInputs), + 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getPhi(unsigned nValueInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::Phi, + uint16_t(nValueInputs), 0, 1, + 1, 0, 0, + Type::anyType(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEffectPhi(unsigned nEffectInputs) //### cache common operands in the static pool +{ + return Operation::create(m_graphPool, Meta::EffectPhi, + 0, uint16_t(nEffectInputs), 1, + 0, 1, 0, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getUnwindDispatch(unsigned nContinuations, int unwindHandlerOffset, + int fallthroughSuccessor) +{ + return OperationWithPayload<UnwindDispatchPayload>::create( + m_graphPool, Meta::UnwindDispatch, + 0, 1, 1, 0, nContinuations, nContinuations, + Type(), Operation::NoFlags, + UnwindDispatchPayload(unwindHandlerOffset, + fallthroughSuccessor)); +} + +Operation *OperationBuilder::getHandleUnwind(int unwindHandlerOffset) +{ + return OperationWithPayload<HandleUnwindPayload>::create(m_graphPool, Meta::HandleUnwind, + 0, 1, 1, 0, 1, 1, + Type(), Operation::NoFlags, + HandleUnwindPayload(unwindHandlerOffset)); +} + +Operation *OperationBuilder::getFrameState(uint16_t frameSize) +{ + if (m_opFrameState == nullptr) + m_opFrameState = Operation::create(m_graphPool, Meta::FrameState, + frameSize, 0, 0, 0, 0, 1, + Type(), Operation::NoFlags); + else + Q_ASSERT(frameSize == m_opFrameState->valueInputCount()); + + return m_opFrameState; +} + +Operation *OperationBuilder::getStart(uint16_t outputCount) +{ + return Operation::create(m_graphPool, Meta::Start, + 0, 0, 0, + outputCount, 1, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getEnd(uint16_t controlInputCount) +{ + return Operation::create(m_graphPool, Meta::End, + 0, 0, controlInputCount, + 0, 0, 0, + Type(), Operation::NoFlags); +} + +inline Operation *createOperation(Operation::Kind kind, QQmlJS::MemoryPool *staticPool) +{ + auto get = [&](uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type (*typeCreator)(), uint8_t flags) { + return Operation::create(staticPool, kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, typeCreator(), + flags); + }; + + using K = Operation::Kind; + using F = Operation::Flags; + const auto none = &Type::noneType; + const auto any = &Type::anyType; + const auto number = &Type::numberType; + const auto boolean = &Type::booleanType; + + switch (kind) { + case K::Undefined: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Undefined, 0, 0, 0, 1, 0, 0, Type::undefinedType(), F::NoFlags, + ConstantPayload(Primitive::undefinedValue())); + case K::Empty: + return OperationWithPayload<ConstantPayload>::create( + staticPool, K::Constant, 0, 0, 0, 1, 0, 0, Type::emptyType(), Operation::NoFlags, + ConstantPayload(Primitive::emptyValue())); + case K::Engine: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::CppFrame: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Function: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + case K::Jump: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::Return: return get(1, 1, 1, 0, 0, 1, none, F::NoFlags); + case K::Branch: return get(1, 0, 1, 0, 0, 2, none, F::HasFrameStateInput | F::NeedsBytecodeOffsets); + case K::IfTrue: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::IfFalse: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::SelectOutput: return get(3, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::Throw: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::OnException: return get(0, 0, 1, 0, 0, 1, none, F::NoFlags); + case K::ThrowReferenceError: return get(1, 1, 1, 0, 1, 1, any, F::NeedsBytecodeOffsets); + case K::UnwindToLabel: return get(2, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::LoadRegExp: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::ScopedLoad: return get(2, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::ScopedStore: return get(3, 1, 0, 0, 1, 0, none, F::NoFlags); + case K::JSLoadElement: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSGetLookup: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreElement: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupStrict: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSSetLookupSloppy: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreProperty: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSLoadGlobalLookup: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreNameSloppy: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSStoreNameStrict: return get(2, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSLoadSuperProperty: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSStoreSuperProperty: return get(2, 1, 1, 0, 1, 2, any, F::CanThrow); + case K::JSLoadClosure: return get(1, 1, 0, 1, 1, 0, any, F::Pure); + case K::JSGetIterator: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNext: return get(2, 1, 1, 2, 1, 1, any, F::NoFlags); + + // special case: see GraphBuilder::generate_IteratorNext + case K::JSIteratorNextForYieldStar: return get(3, 1, 1, 2, 1, 1, any, F::NoFlags); + + case K::JSIteratorClose: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteProperty: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDeleteName: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSIn: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSInstanceOf: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLLoadScopeObjectProperty: return get(3, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLStoreScopeObjectProperty: return get(3, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::QMLLoadContextObjectProperty: return get(3, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLStoreContextObjectProperty: return get(3, 1, 1, 1, 1, 2, none, F::CanThrow); + case K::QMLLoadIdObject: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSGreaterEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessThan: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSLessEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + case K::JSStrictEqual: return get(2, 1, 1, 1, 1, 2, boolean, F::CanThrow); + + case K::JSAdd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSSubtract: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSMultiply: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSDivide: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSModulo: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSExponentiate: return get(2, 1, 1, 1, 1, 2, number, F::CanThrow); + + case K::JSBitAnd: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitOr: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSBitXor: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSUnsignedShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftRight: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSShiftLeft: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + + case K::JSNegate: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::JSToNumber: return get(1, 1, 1, 1, 1, 2, number, F::CanThrow); + case K::Alloca: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + //### it is questionable if VAAlloc/VASeal need effect edges + case K::VAAlloc: return get(1, 1, 0, 1, 1, 0, none, F::NoFlags); + + case K::VAStore: return get(3, 0, 0, 1, 0, 0, none, F::NoFlags); + + case K::JSTypeofName: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSTypeofValue: return get(1, 0, 0, 1, 0, 0, any, F::Pure); + case K::JSDeclareVar: return get(2, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::JSDestructureRestElement: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::QMLLoadContext: return get(0, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::QMLLoadImportedScripts: return get(0, 0, 0, 1, 0, 0, any, F::NoFlags); + + case K::JSCreateCallContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateCatchContext: return get(2, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCreateWithContext: return get(1, 1, 1, 1, 1, 1, any, F::NoFlags); + case K::JSCreateBlockContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSCloneBlockContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + case K::JSCreateScriptContext: return get(1, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::JSPopScriptContext: return get(0, 1, 1, 1, 1, 1, none, F::NoFlags); + case K::PopContext: return get(0, 1, 1, 0, 1, 1, none, F::NoFlags); + + case K::JSThisToObject: return get(1, 1, 1, 0, 1, 2, any, F::NoFlags); + case K::JSCreateMappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateUnmappedArgumentsObject: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::JSCreateRestParameter: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::JSLoadSuperConstructor: return get(1, 1, 1, 1, 1, 2, any, F::NoFlags); + case K::JSThrowOnNullOrUndefined: return get(1, 1, 1, 0, 1, 2, none, F::CanThrow); + case K::JSGetTemplateObject: return get(1, 0, 0, 1, 0, 0, any, F::NoFlags); + case K::StoreThis: return get(1, 1, 0, 1, 1, 0, any, F::NoFlags); + + case K::GetException: return get(0, 1, 0, 1, 1, 0, any, F::NoFlags); + case K::SetException: return get(1, 1, 0, 0, 1, 0, any, F::NoFlags); + + case K::ToObject: return get(1, 1, 1, 1, 1, 2, any, F::CanThrow); + case K::ToBoolean: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::IsEmpty: return get(1, 0, 0, 1, 0, 0, boolean, F::Pure); + + case K::BooleanNot: return get(1, 0, 0, 1, 0, 0, boolean, F::NoFlags); + case K::HasException: return get(1, 1, 0, 1, 1, 0, boolean, F::NoFlags); + + case K::Swap: return get(0, 0, 0, 0, 0, 0, none, F::NoFlags); + case K::Move: return get(1, 0, 0, 1, 0, 0, none, F::NoFlags); + + default: // Non-static operations: + return nullptr; + } +} + +Operation *OperationBuilder::staticOperation(Operation::Kind kind) +{ + static QAtomicPointer<Operation *> ops; + if (Operation **staticOps = ops.load()) + return staticOps[kind]; + + static QAtomicInt initializing = 0; + if (initializing.testAndSetOrdered(0, 1)) { + // This is safe now, because we can only run this piece of code once during the life time + // of the application as we can only change initializing from 0 to 1 once. + Operation **staticOps = new Operation *[Meta::KindsEnd]; + static QQmlJS::MemoryPool pool; + for (int i = 0; i < Meta::KindsEnd; ++i) + staticOps[i] = createOperation(Operation::Kind(i), &pool); + bool success = ops.testAndSetOrdered(nullptr, staticOps); + Q_ASSERT(success); + } else { + // Unfortunately we need to busy wait now until the other thread finishes the static + // initialization; + while (!ops.load()) {} + } + + return ops.load()[kind]; +} + +Operation *OperationBuilder::getJSVarArgsCall(Operation::Kind kind, uint16_t argc) +{ + return Operation::create(m_graphPool, kind, + argc, 1, 1, 1, 1, 2, + Type::anyType(), Operation::CanThrow); +} + +Operation *OperationBuilder::getJSTailCall(uint16_t argc) +{ + return Operation::create(m_graphPool, Meta::JSTailCall, + argc, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getTailCall() +{ + // special varargs call, takes cppframe, engine, func, thisObject, argv, argc + return Operation::create(m_graphPool, Meta::TailCall, + 6, 1, 1, 0, 0, 1, + Type(), Operation::NoFlags); +} + +Operation *OperationBuilder::getCall(Operation::Kind callee) +{ + const bool canThrow = CallPayload::canThrow(callee); + const Type retTy = CallPayload::returnType(callee); + uint16_t nControlInputs = 0; + uint16_t nControlOutputs = 0; + if (canThrow) { + nControlInputs = 1; + nControlOutputs += 2; + } + if (CallPayload::changesContext(callee)) { + nControlInputs = 1; + nControlOutputs = std::max<uint16_t>(nControlInputs, 1); + } + if (callee == Meta::Throw || callee == Meta::ThrowReferenceError || + callee == Meta::JSIteratorNext || callee == Meta::JSIteratorNextForYieldStar) { + nControlInputs = 1; + nControlOutputs = 1; + } + Operation::Flags flags = Operation::NoFlags; + if (canThrow) + flags = Operation::Flags(flags | Operation::CanThrow); + if (CallPayload::isPure(callee)) + flags = Operation::Flags(flags | Operation::Pure); + const uint16_t nEffects = (flags & Operation::Pure) ? 0 : 1; + const uint16_t nValueOutputs = retTy.isNone() ? 0 : 1; + const uint16_t nValueInputs = CallPayload::argc(callee); + + return OperationWithPayload<CallPayload>::create( + m_graphPool, Meta::Call, + nValueInputs, nEffects, nControlInputs, + nValueOutputs, nEffects, nControlOutputs, + retTy, flags, + CallPayload(callee)); +} + +Operation *OperationBuilder::getVASeal(uint16_t nElements) +{ + return Operation::create(m_graphPool, Meta::VASeal, + nElements + 1, 1, 0, 1, 1, 0, + Type::anyType(), Operation::NoFlags); +} + +QString Operation::debugString() const +{ + switch (kind()) { + + case Meta::Constant: + return QStringLiteral("Constant[%1]").arg(ConstantPayload::get(*this)->debugString()); + case Meta::Parameter: + return QStringLiteral("Parameter[%1]").arg(ParameterPayload::get(*this)->debugString()); + case Meta::Call: + return QStringLiteral("Call[%1]").arg(CallPayload::get(*this)->debugString()); + case Meta::UnwindDispatch: + return QStringLiteral("UnwindDispatch[%1]").arg(UnwindDispatchPayload::get(*this) + ->debugString()); + case Meta::HandleUnwind: + return QStringLiteral("HandleUnwind[%1]").arg(HandleUnwindPayload::get(*this) + ->debugString()); + + default: + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(kind())); + } +} + +QString ConstantPayload::debugString() const +{ + return debugString(m_value); +} + +QString ConstantPayload::debugString(QV4::Value v) +{ + if (v.isManaged()) + return QString().sprintf("Ptr: %p", v.heapObject()); + if (v.isEmpty()) + return QStringLiteral("empty"); + return v.toQStringNoThrow(); +} + +QString ParameterPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_index); +} + +QString UnwindDispatchPayload::debugString() const +{ + return QStringLiteral("%1, %2").arg(QString::number(m_fallthroughSuccessor), + QString::number(m_unwindHandlerOffset)); +} + +QString HandleUnwindPayload::debugString() const +{ + return QStringLiteral("%1").arg(m_unwindHandlerOffset); +} + +static Type translateType(RuntimeSupport::ArgumentType t) +{ + switch (t) { + case RuntimeSupport::ArgumentType::Int: return Type::int32Type(); + case RuntimeSupport::ArgumentType::Bool: return Type::booleanType(); + case RuntimeSupport::ArgumentType::Void: return Type(); + case RuntimeSupport::ArgumentType::Engine: return Type::rawPointerType(); + case RuntimeSupport::ArgumentType::ValueRef: return Type::anyType(); + case RuntimeSupport::ArgumentType::ValueArray: return Type::anyType(); + case RuntimeSupport::ArgumentType::ReturnedValue: return Type::anyType(); + default: Q_UNREACHABLE(); + } +} + +template<template<typename Operation> class M /* MetaOperation */, typename ReturnValue> +static ReturnValue operateOnRuntimeCall(Operation::Kind kind, bool abortOnMissingCall = true) +{ + using K = Operation::Kind; + using R = Runtime; + + switch (kind) { + case K::Throw: return M<R::ThrowException>::doIt(); + case K::ThrowReferenceError: return M<R::ThrowReferenceError>::doIt(); + + case K::JSEqual: return M<R::CompareEqual>::doIt(); + case K::JSGreaterThan: return M<R::CompareGreaterThan>::doIt(); + case K::JSGreaterEqual: return M<R::CompareGreaterEqual>::doIt(); + case K::JSLessThan: return M<R::CompareLessThan>::doIt(); + case K::JSLessEqual: return M<R::CompareLessEqual>::doIt(); + case K::JSStrictEqual: return M<R::CompareStrictEqual>::doIt(); + + case K::JSBitAnd: return M<R::BitAnd>::doIt(); + case K::JSBitOr: return M<R::BitOr>::doIt(); + case K::JSBitXor: return M<R::BitXor>::doIt(); + case K::JSUnsignedShiftRight: return M<R::UShr>::doIt(); + case K::JSShiftRight: return M<R::Shr>::doIt(); + case K::JSShiftLeft: return M<R::Shl>::doIt(); + + case K::JSAdd: return M<R::Add>::doIt(); + case K::JSSubtract: return M<R::Sub>::doIt(); + case K::JSMultiply: return M<R::Mul>::doIt(); + case K::JSDivide: return M<R::Div>::doIt(); + case K::JSModulo: return M<R::Mod>::doIt(); + case K::JSExponentiate: return M<R::Exp>::doIt(); + + case K::ToBoolean: return M<R::ToBoolean>::doIt(); + case K::ToObject: return M<R::ToObject>::doIt(); + + case K::JSNegate: return M<R::UMinus>::doIt(); + case K::JSToNumber: return M<R::ToNumber>::doIt(); + + case K::JSLoadName: return M<R::LoadName>::doIt(); + case K::JSLoadElement: return M<R::LoadElement>::doIt(); + case K::JSStoreElement: return M<R::StoreElement>::doIt(); + case K::JSGetLookup: return M<R::GetLookup>::doIt(); + case K::JSSetLookupStrict: return M<R::SetLookupStrict>::doIt(); + case K::JSSetLookupSloppy: return M<R::SetLookupSloppy>::doIt(); + case K::JSLoadProperty: return M<R::LoadProperty>::doIt(); + case K::JSStoreProperty: return M<R::StoreProperty>::doIt(); + case K::JSLoadGlobalLookup: return M<R::LoadGlobalLookup>::doIt(); + case K::JSStoreNameSloppy: return M<R::StoreNameSloppy>::doIt(); + case K::JSStoreNameStrict: return M<R::StoreNameStrict>::doIt(); + case K::JSLoadSuperProperty: return M<R::LoadSuperProperty>::doIt(); + case K::JSStoreSuperProperty: return M<R::StoreSuperProperty>::doIt(); + case K::JSLoadClosure: return M<R::Closure>::doIt(); + case K::JSGetIterator: return M<R::GetIterator>::doIt(); + case K::JSIteratorNext: return M<R::IteratorNext>::doIt(); + case K::JSIteratorNextForYieldStar: return M<R::IteratorNextForYieldStar>::doIt(); + case K::JSIteratorClose: return M<R::IteratorClose>::doIt(); + case K::JSDeleteProperty: return M<R::DeleteProperty>::doIt(); + case K::JSDeleteName: return M<R::DeleteName>::doIt(); + case K::JSIn: return M<R::In>::doIt(); + case K::JSInstanceOf: return M<R::Instanceof>::doIt(); + case K::QMLLoadScopeObjectProperty: return M<R::LoadQmlScopeObjectProperty>::doIt(); + case K::QMLStoreScopeObjectProperty: return M<R::StoreQmlScopeObjectProperty>::doIt(); + case K::QMLLoadContextObjectProperty: return M<R::LoadQmlContextObjectProperty>::doIt(); + case K::QMLStoreContextObjectProperty: return M<R::StoreQmlContextObjectProperty>::doIt(); + case K::QMLLoadIdObject: return M<R::LoadQmlIdObject>::doIt(); + + case K::JSTypeofName: return M<R::TypeofName>::doIt(); + case K::JSTypeofValue: return M<R::TypeofValue>::doIt(); + case K::JSDeclareVar: return M<R::DeclareVar>::doIt(); + case K::JSDestructureRestElement: return M<R::DestructureRestElement>::doIt(); + case K::QMLLoadContext: return M<R::LoadQmlContext>::doIt(); + case K::QMLLoadImportedScripts: return M<R::LoadQmlImportedScripts>::doIt(); + case K::JSThisToObject: return M<R::ConvertThisToObject>::doIt(); + case K::JSCreateMappedArgumentsObject: return M<R::CreateMappedArgumentsObject>::doIt(); + case K::JSCreateUnmappedArgumentsObject: return M<R::CreateUnmappedArgumentsObject>::doIt(); + case K::JSCreateRestParameter: return M<R::CreateRestParameter>::doIt(); + case K::JSLoadSuperConstructor: return M<R::LoadSuperConstructor>::doIt(); + case K::JSThrowOnNullOrUndefined: return M<R::ThrowOnNullOrUndefined>::doIt(); + + case K::JSCreateCallContext: return M<R::PushCallContext>::doIt(); + case K::JSCreateCatchContext: return M<R::PushCatchContext>::doIt(); + case K::JSCreateWithContext: return M<R::PushWithContext>::doIt(); + case K::JSCreateBlockContext: return M<R::PushBlockContext>::doIt(); + case K::JSCloneBlockContext: return M<R::CloneBlockContext>::doIt(); + case K::JSCreateScriptContext: return M<R::PushScriptContext>::doIt(); + case K::JSPopScriptContext: return M<R::PopScriptContext>::doIt(); + + case K::LoadRegExp: return M<R::RegexpLiteral>::doIt(); + case K::JSGetTemplateObject: return M<R::GetTemplateObject>::doIt(); + + case K::JSCallName: return M<R::CallName>::doIt(); + case K::JSCallValue: return M<R::CallValue>::doIt(); + case K::JSCallElement: return M<R::CallElement>::doIt(); + case K::JSCallLookup: return M<R::CallPropertyLookup>::doIt(); + case K::JSCallProperty: return M<R::CallProperty>::doIt(); + case K::JSCallGlobalLookup: return M<R::CallGlobalLookup>::doIt(); + case K::JSCallPossiblyDirectEval: return M<R::CallPossiblyDirectEval>::doIt(); + case K::JSCallWithReceiver: return M<R::CallWithReceiver>::doIt(); + case K::JSDefineObjectLiteral: return M<R::ObjectLiteral>::doIt(); + case K::JSDefineArray: return M<R::ArrayLiteral>::doIt(); + case K::JSCallWithSpread: return M<R::CallWithSpread>::doIt(); + case K::JSConstruct: return M<R::Construct>::doIt(); + case K::JSConstructWithSpread: return M<R::ConstructWithSpread>::doIt(); + case K::JSTailCall: return M<R::TailCall>::doIt(); + case K::JSCreateClass: return M<R::CreateClass>::doIt(); + default: + if (abortOnMissingCall) + Q_UNREACHABLE(); + else + return ReturnValue(); + } +} + +template<typename Method> +struct IsRuntimeMethodOperation +{ + static constexpr bool doIt() { return true; } +}; + +bool CallPayload::isRuntimeCall(Operation::Kind m) +{ + return operateOnRuntimeCall<IsRuntimeMethodOperation, bool>(m, false); +} + +QString CallPayload::debugString() const +{ + return QString::fromLatin1(QMetaEnum::fromType<Meta::OpKind>().valueToKey(m_callee)); +} + +template<typename Method> +struct MethodArgcOperation +{ + static constexpr unsigned doIt() { return RuntimeSupport::argumentCount<Method>(); } +}; + +unsigned CallPayload::argc(Operation::Kind callee) +{ + return operateOnRuntimeCall<MethodArgcOperation, unsigned>(callee); +} + +template<typename Method> struct MethodArg1TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg1Type<Method>(); } }; +template<typename Method> struct MethodArg2TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg2Type<Method>(); } }; +template<typename Method> struct MethodArg3TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg3Type<Method>(); } }; +template<typename Method> struct MethodArg4TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg4Type<Method>(); } }; +template<typename Method> struct MethodArg5TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg5Type<Method>(); } }; +template<typename Method> struct MethodArg6TyOperation { static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::arg6Type<Method>(); } }; + +static RuntimeSupport::ArgumentType untranslatedArgumentType(Operation::Kind m, unsigned arg) +{ + if (m == Meta::JSTailCall) { + if (arg < 4) + return RuntimeSupport::ArgumentType::ValueRef; + else + return RuntimeSupport::ArgumentType::Invalid; + } + + switch (arg) { + case 0: return operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m); + case 1: return operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m); + case 2: return operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m); + case 3: return operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m); + case 4: return operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m); + case 5: return operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m); + default: return RuntimeSupport::ArgumentType::Invalid; + } +} + +bool CallPayload::needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType) +{ + auto argTy = untranslatedArgumentType(m, arg); + if (argTy == RuntimeSupport::ArgumentType::ValueArray) + return true; + if (argTy != RuntimeSupport::ArgumentType::ValueRef) + return false; + + if (op->kind() == Meta::Constant) + return true; + + return !nodeType.isObject() && !nodeType.isRawPointer() && !nodeType.isAny(); +} + +template<typename Method> +struct MethodRetTyOperation +{ + static constexpr RuntimeSupport::ArgumentType doIt() { return RuntimeSupport::retType<Method>(); } +}; + +Type CallPayload::returnType(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return Type(); + + auto t = operateOnRuntimeCall<MethodRetTyOperation, RuntimeSupport::ArgumentType>(m); + return translateType(t); +} + +static int firstArgumentPositionForType(Operation::Kind m, RuntimeSupport::ArgumentType type) +{ + if (operateOnRuntimeCall<MethodArg1TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 1; + if (operateOnRuntimeCall<MethodArg2TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 2; + if (operateOnRuntimeCall<MethodArg3TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 3; + if (operateOnRuntimeCall<MethodArg4TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 4; + if (operateOnRuntimeCall<MethodArg5TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 5; + if (operateOnRuntimeCall<MethodArg6TyOperation, RuntimeSupport::ArgumentType>(m) == type) + return 6; + return -1; +} + +unsigned CallPayload::varArgsStart(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return 4 - 1; + + int pos = firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) - 1; + Q_ASSERT(pos >= 0); + return pos; +} + +bool CallPayload::isVarArgsCall(Operation::Kind m) +{ + if (m == Meta::JSTailCall) + return true; + if (lastArgumentIsOutputValue(m)) + return false; + return firstArgumentPositionForType(m, RuntimeSupport::ArgumentType::ValueArray) != -1; +} + +bool CallPayload::isVarArgsCall() const +{ + return isVarArgsCall(m_callee); +} + +template<typename Method> +struct MethodsLastArgumentIsOutputValue +{ + static constexpr bool doIt() { return Method::lastArgumentIsOutputValue; } +}; + +bool CallPayload::lastArgumentIsOutputValue(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodsLastArgumentIsOutputValue, bool>(m); +} + +template<typename Method> +struct MethodChangesContext +{ + static constexpr bool doIt() { return Method::changesContext; } +}; + +bool CallPayload::changesContext(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodChangesContext, bool>(m); +} + +template<typename Method> +struct MethodIsPure +{ + static constexpr bool doIt() { return Method::pure; } +}; + +bool CallPayload::isPure(Operation::Kind m) +{ + return operateOnRuntimeCall<MethodIsPure, bool>(m); +} + +template<typename Method> +struct MethodCanThrow +{ + static constexpr bool doIt() { return Method::throws; } +}; + +bool CallPayload::canThrow(Operation::Kind m) +{ + switch (m) { + case Meta::Throw: Q_FALLTHROUGH(); + case Meta::ThrowReferenceError: + // the execution path following these instructions is already linked up to the exception handler + return false; + case Meta::JSIteratorNext: Q_FALLTHROUGH(); + case Meta::JSIteratorNextForYieldStar: + // special case: see GraphBuilder::generate_IteratorNext + return false; + default: + return operateOnRuntimeCall<MethodCanThrow, bool>(m); + } +} + +bool CallPayload::takesEngineAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Engine; +} + +bool CallPayload::takesFunctionAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Function; +} + +bool CallPayload::takesFrameAsArg(Operation::Kind m, int arg) +{ + return untranslatedArgumentType(m, arg) == RuntimeSupport::ArgumentType::Frame; +} + +template<typename Method> +struct GetMethodPtr +{ + static constexpr void *doIt() { return reinterpret_cast<void *>(&Method::call); } +}; + +void *CallPayload::getMethodPtr(Operation::Kind m) +{ + return operateOnRuntimeCall<GetMethodPtr, void *>(m); +} + +} // IR namespace +} // QV4 namespace +QT_END_NAMESPACE diff --git a/src/qml/jit/qv4operation_p.h b/src/qml/jit/qv4operation_p.h new file mode 100644 index 0000000000..8b66e58d4b --- /dev/null +++ b/src/qml/jit/qv4operation_p.h @@ -0,0 +1,574 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4OPERATION_P_H +#define QV4OPERATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qv4ir_p.h> +#include <private/qqmljsmemorypool_p.h> + +#include <QtCore/qatomic.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { + +namespace Meta { +enum OpKind: uint16_t { + FrameState, + Start, + End, + + Undefined, + Constant, + Parameter, + Empty, + Engine, + CppFrame, + Function, + + Jump, + Return, + JSTailCall, + TailCall, + Branch, + IfTrue, + IfFalse, + Region, + OnException, + Phi, + EffectPhi, + SelectOutput, + UnwindDispatch, + UnwindToLabel, + HandleUnwind, + Throw, + ThrowReferenceError, + + Call, + + LoadRegExp, + ScopedLoad, + ScopedStore, + + JSLoadElement, + JSStoreElement, + JSGetLookup, + JSSetLookupStrict, + JSSetLookupSloppy, + JSLoadProperty, + JSStoreProperty, + JSLoadName, + JSLoadGlobalLookup, + JSStoreNameSloppy, + JSStoreNameStrict, + JSLoadSuperProperty, + JSStoreSuperProperty, + JSLoadClosure, + JSGetIterator, + JSIteratorNext, + JSIteratorNextForYieldStar, + JSIteratorClose, + JSDeleteProperty, + JSDeleteName, + JSIn, + JSInstanceOf, + /* ok, these are qml object ops, but we don't care for now and treat them a s JS */ + QMLLoadScopeObjectProperty, + QMLStoreScopeObjectProperty, + QMLLoadContextObjectProperty, + QMLStoreContextObjectProperty, + QMLLoadIdObject, + + JSEqual, + JSGreaterThan, + JSGreaterEqual, + JSLessThan, + JSLessEqual, + JSStrictEqual, + + JSAdd, + JSSubtract, + JSMultiply, + JSDivide, + JSModulo, + JSExponentiate, + + JSBitAnd, + JSBitOr, + JSBitXor, + JSUnsignedShiftRight, + JSShiftRight, + JSShiftLeft, + + JSNegate, + JSToNumber, + + JSCallName, + JSCallValue, + JSCallElement, + JSCallProperty, + JSCallLookup, + JSCallGlobalLookup, + JSCallPossiblyDirectEval, + JSCallWithReceiver, + JSCallWithSpread, + JSDefineObjectLiteral, + JSDefineArray, + JSCreateClass, + JSConstruct, + JSConstructWithSpread, + /* ok, these are qml vararg calls, but we don't care for now and treat them as JS */ + QMLCallScopeObjectProperty, + QMLCallContextObjectProperty, + + JSTypeofName, + JSTypeofValue, + JSDeclareVar, + JSDestructureRestElement, + QMLLoadContext, + QMLLoadImportedScripts, + JSThisToObject, + JSCreateMappedArgumentsObject, + JSCreateUnmappedArgumentsObject, + JSCreateRestParameter, + JSLoadSuperConstructor, + JSThrowOnNullOrUndefined, + JSGetTemplateObject, + StoreThis, + + JSCreateCallContext, + JSCreateCatchContext, + JSCreateWithContext, + JSCreateBlockContext, + JSCloneBlockContext, + JSCreateScriptContext, + JSPopScriptContext, + PopContext, + + GetException, + SetException, + + ToObject, + ToBoolean, + + //### do we need this? Or should a later phase generate JumpIsEmpty? + IsEmpty, + + Alloca, + VAAlloc, + VAStore, + VASeal, + + BooleanNot, + HasException, + + // Low level, used by the register allocator and stack allocator: + Swap, + Move, + KindsEnd +}; +Q_NAMESPACE +Q_ENUM_NS(OpKind) +} // namespace Ops + +class Operation +{ + Q_DISABLE_COPY_MOVE(Operation) + +public: + using Kind = Meta::OpKind; + + enum Flags: uint8_t { + NoFlags = 0, + ThrowsFlag = 1 << 0, + Pure = 1 << 1, // no read/write side effect, cannot throw, cannot deopt, and is idempotent + NeedsBytecodeOffsets = 1 << 2, + + CanThrow = ThrowsFlag | NeedsBytecodeOffsets, + + HasFrameStateInput = 1 << 3, + }; + +public: + static Operation *create(QQmlJS::MemoryPool *pool, Kind kind, uint16_t inValueCount, + uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, + uint16_t outControlCount, Type type, uint8_t flags) + { + return pool->New<Operation>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, Flags(flags)); + } + + Kind kind() const + { return m_kind; } + + bool isConstant() const + { + switch (kind()) { + case Meta::Undefined: Q_FALLTHROUGH(); + case Meta::Constant: + case Meta::Empty: + return true; + default: + return false; + } + } + + QString debugString() const; + + uint16_t valueInputCount() const { return m_inValueCount; } + uint16_t effectInputCount() const { return m_inEffectCount; } + uint16_t controlInputCount() const { return m_inControlCount; } + uint16_t valueOutputCount() const { return m_outValueCount; } + uint16_t effectOutputCount() const { return m_outEffectCount; } + uint16_t controlOutputCount() const { return m_outControlCount; } + + unsigned indexOfFirstEffect() const { return m_inValueCount; } + unsigned indexOfFirstControl() const { return m_inValueCount + m_inEffectCount; } + unsigned indexOfFrameStateInput() const + { + return hasFrameStateInput() ? indexOfFirstControl() + m_inControlCount + : std::numeric_limits<unsigned>::max(); + } + + Type type() const + { return m_type; } + + bool canThrow() const + { return m_flags & ThrowsFlag; } + + bool isPure() const + { return m_flags & Pure; } + + bool needsBytecodeOffsets() const + { return m_flags & NeedsBytecodeOffsets; } + + bool hasFrameStateInput() const + { return m_flags & HasFrameStateInput; } + + unsigned totalInputCount() const + { + return valueInputCount() + effectInputCount() + controlInputCount() + + (hasFrameStateInput() ? 1 : 0); + } + unsigned totalOutputCount() const { return valueOutputCount() + effectOutputCount() + controlOutputCount(); } + +protected: + friend class QQmlJS::MemoryPool; + Operation(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, uint8_t flags) + : m_kind(kind) + , m_inValueCount(inValueCount) + , m_inEffectCount(inEffectCount) + , m_inControlCount(inControlCount) + , m_outValueCount(outValueCount) + , m_outEffectCount(outEffectCount) + , m_outControlCount(outControlCount) + , m_type(type) + , m_flags(Flags(flags)) + { + } + + ~Operation() = default; + +private: + Kind m_kind; + uint16_t m_inValueCount; + uint16_t m_inEffectCount; + uint16_t m_inControlCount; + uint16_t m_outValueCount; + uint16_t m_outEffectCount; + uint16_t m_outControlCount; + Type m_type; + Flags m_flags; +}; + +template <typename Payload> +class OperationWithPayload: public Operation +{ +public: + static OperationWithPayload *create(QQmlJS::MemoryPool *pool, Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + { + return pool->New<OperationWithPayload>(kind, inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags, payload); + } + + const Payload &payload() const + { return m_payload; } + +protected: + friend class QQmlJS::MemoryPool; + OperationWithPayload(Kind kind, + uint16_t inValueCount, uint16_t inEffectCount, uint16_t inControlCount, + uint16_t outValueCount, uint16_t outEffectCount, uint16_t outControlCount, + Type type, Flags flags, Payload payload) + : Operation(kind, + inValueCount, inEffectCount, inControlCount, + outValueCount, outEffectCount, outControlCount, + type, flags) + , m_payload(payload) + {} + + ~OperationWithPayload() = default; + +private: + Payload m_payload; +}; + +class ConstantPayload +{ +public: + explicit ConstantPayload(QV4::Value v) + : m_value(v) + {} + + QV4::Value value() const + { return m_value; } + + static const ConstantPayload *get(const Operation &op) + { + if (op.kind() != Meta::Constant) + return nullptr; + + return &static_cast<const OperationWithPayload<ConstantPayload>&>(op).payload(); + } + + QString debugString() const; + static QString debugString(QV4::Value v); + +private: + QV4::Value m_value; +}; + +class ParameterPayload +{ +public: + ParameterPayload(size_t index, Function::StringId stringId) + : m_index(index) + , m_stringId(stringId) + {} + + size_t parameterIndex() const + { return m_index; } + + Function::StringId stringId() const + { return m_stringId; } + + static const ParameterPayload *get(const Operation &op) + { + if (op.kind() != Meta::Parameter) + return nullptr; + + return &static_cast<const OperationWithPayload<ParameterPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + size_t m_index; + Function::StringId m_stringId; +}; + +class CallPayload +{ +public: + CallPayload(Operation::Kind callee) + : m_callee(callee) + {} + + static const CallPayload *get(const Operation &op) + { + if (op.kind() != Meta::Call) + return nullptr; + + return &static_cast<const OperationWithPayload<CallPayload>&>(op).payload(); + } + + static bool isRuntimeCall(Operation::Kind m); + + Operation::Kind callee() const { return m_callee; } + QString debugString() const; + + unsigned argc() const { return argc(m_callee); } + static unsigned argc(Operation::Kind callee); + static bool needsStorageOnJSStack(Operation::Kind m, unsigned arg, const Operation *op, + Type nodeType); + static Type returnType(Operation::Kind m); + static int engineArgumentPosition(Operation::Kind m); + static int functionArgumentPosition(Operation::Kind m); + + static constexpr unsigned NoVarArgs = std::numeric_limits<unsigned>::max(); + static unsigned varArgsStart(Operation::Kind m); + static bool isVarArgsCall(Operation::Kind m); + bool isVarArgsCall() const; + static bool lastArgumentIsOutputValue(Operation::Kind m); + static bool changesContext(Operation::Kind m); + static bool isPure(Operation::Kind m); + static bool canThrow(Operation::Kind m); + static bool takesEngineAsArg(Operation::Kind m, int arg); + static bool takesFunctionAsArg(Operation::Kind m, int arg); + static bool takesFrameAsArg(Operation::Kind m, int arg); + static void *getMethodPtr(Operation::Kind m); + +private: + Operation::Kind m_callee; +}; + +class UnwindDispatchPayload +{ +public: + UnwindDispatchPayload(int unwindHandlerOffset, int fallthroughSuccessor) + : m_unwindHandlerOffset(unwindHandlerOffset) + , m_fallthroughSuccessor(fallthroughSuccessor) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + int fallthroughSuccessor() const //### unused... + { return m_fallthroughSuccessor; } + + static const UnwindDispatchPayload *get(const Operation &op) + { + if (op.kind() != Meta::UnwindDispatch) + return nullptr; + + return &static_cast<const OperationWithPayload<UnwindDispatchPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; + int m_fallthroughSuccessor; +}; + +class HandleUnwindPayload +{ +public: + HandleUnwindPayload(int unwindHandlerOffset) + : m_unwindHandlerOffset(unwindHandlerOffset) + {} + + int unwindHandlerOffset() const + { return m_unwindHandlerOffset; } + + static const HandleUnwindPayload *get(const Operation &op) + { + if (op.kind() != Meta::HandleUnwind) + return nullptr; + + return &static_cast<const OperationWithPayload<HandleUnwindPayload>&>(op).payload(); + } + + QString debugString() const; + +private: + int m_unwindHandlerOffset; +}; + +class OperationBuilder +{ + Q_DISABLE_COPY_MOVE(OperationBuilder) + + friend class QQmlJS::MemoryPool; + OperationBuilder(QQmlJS::MemoryPool *graphPool); + +public: + static OperationBuilder *create(QQmlJS::MemoryPool *pool); + ~OperationBuilder() = delete; + + Operation *getConstant(QV4::Value v); + Operation *getFrameState(uint16_t frameSize); + Operation *getStart(uint16_t outputCount); + Operation *getEnd(uint16_t controlInputCount); + Operation *getParam(unsigned index, Function::StringId name); + Operation *getRegion(unsigned nControlInputs); + Operation *getPhi(unsigned nValueInputs); + Operation *getEffectPhi(unsigned nEffectInputs); + Operation *getUnwindDispatch(unsigned nControlOutputs, int unwindHandlerOffset, int fallthroughSuccessor); + Operation *getHandleUnwind(int unwindHandlerOffset); + + template<Operation::Kind kind> + Operation *get() { + return staticOperation(kind); + } + + Operation *getVASeal(uint16_t nElements); + + Operation *getJSVarArgsCall(Operation::Kind kind, uint16_t argc); + Operation *getJSTailCall(uint16_t argc); + Operation *getTailCall(); + + Operation *getCall(Operation::Kind callee); + +private: + QQmlJS::MemoryPool *m_graphPool; // used to store per-graph nodes + Operation *m_opFrameState = nullptr; + static Operation *staticOperation(Operation::Kind kind); +}; + +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4OPERATION_P_H diff --git a/src/qml/jit/qv4runtimesupport_p.h b/src/qml/jit/qv4runtimesupport_p.h new file mode 100644 index 0000000000..0dc6022331 --- /dev/null +++ b/src/qml/jit/qv4runtimesupport_p.h @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4RUNTIMESUPPORT_P_H +#define QV4RUNTIMESUPPORT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <qv4runtimeapi_p.h> + +QT_REQUIRE_CONFIG(qml_tracing); + +QT_BEGIN_NAMESPACE + +namespace QV4 { +namespace IR { +namespace RuntimeSupport { + +template <typename T> +struct CountArguments { + static constexpr unsigned count = 0; +}; +template <typename RetTy, typename... Args> +struct CountArguments<RetTy (*)(Args... args)> { + static constexpr unsigned count = sizeof...(Args) ; +}; + +template<typename M> +static constexpr unsigned argumentCount() { + using type = decltype(&M::call); + return CountArguments<type>::count; +} + +enum class ArgumentType { + Invalid, + Engine, + Frame, + Function, + ValueRef, + ValueArray, + ReturnedValue, + Int, + Bool, + Void, +}; + + +template <typename T> +struct JavaScriptType +{ + // No default type. We want to make sure everything we do is actually recognized. +}; + +template <typename T> +struct ReturnValue +{ + // No default type. +}; + +template <int I, typename T> +struct Argument +{ + // For simplicity, we add a default here. Otherwise we would need to spell out more + // combinations of I and number of arguments of T. + static constexpr ArgumentType type = ArgumentType::Invalid; +}; + +template <typename RetTy, typename T, typename... Args> +struct Argument<1, RetTy (*)(T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename T, typename... Args> +struct Argument<2, RetTy (*)(Arg1, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, typename T, + typename... Args> +struct Argument<3, RetTy (*)(Arg1, Arg2, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename T, typename... Args> +struct Argument<4, RetTy (*)(Arg1, Arg2, Arg3, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename T, typename... Args> +struct Argument<5, RetTy (*)(Arg1, Arg2, Arg3, Arg4, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename Arg1, typename Arg2, + typename Arg3, typename Arg4, typename Arg5, typename T, typename... Args> +struct Argument<6, RetTy (*)(Arg1, Arg2, Arg3, Arg4, Arg5, T, Args... args)> { + static constexpr ArgumentType type = JavaScriptType<T>::type; +}; + +template <typename RetTy, typename... Args> +struct ReturnValue<RetTy (*)(Args... args)> { + static constexpr ArgumentType type = JavaScriptType<RetTy>::type; +}; + +template<> +struct JavaScriptType<QV4::ExecutionEngine *> +{ + static constexpr ArgumentType type = ArgumentType::Engine; +}; + +template<> +struct JavaScriptType<QV4::CppStackFrame *> +{ + static constexpr ArgumentType type = ArgumentType::Frame; +}; + +template<> +struct JavaScriptType<QV4::Function *> +{ + static constexpr ArgumentType type = ArgumentType::Function; +}; + +template<> +struct JavaScriptType<const QV4::Value &> +{ + static constexpr ArgumentType type = ArgumentType::ValueRef; +}; + +template<> +// We need to pass Value * in order to match a parmeter Value[]. +struct JavaScriptType<QV4::Value *> +{ + static constexpr ArgumentType type = ArgumentType::ValueArray; +}; + +template<> +struct JavaScriptType<int> +{ + static constexpr ArgumentType type = ArgumentType::Int; +}; + +template<> +struct JavaScriptType<QV4::Bool> +{ + static constexpr ArgumentType type = ArgumentType::Bool; +}; + +template<> +struct JavaScriptType<QV4::ReturnedValue> +{ + static constexpr ArgumentType type = ArgumentType::ReturnedValue; +}; + +template<> +struct JavaScriptType<void> +{ + static constexpr ArgumentType type = ArgumentType::Void; +}; + +template<typename M> +static constexpr ArgumentType retType() { + using Type = decltype(&M::call); + return ReturnValue<Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg1Type() { + using Type = decltype(&M::call); + return Argument<1, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg2Type() { + using Type = decltype(&M::call); + return Argument<2, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg3Type() { + using Type = decltype(&M::call); + return Argument<3, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg4Type() { + using Type = decltype(&M::call); + return Argument<4, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg5Type() { + using Type = decltype(&M::call); + return Argument<5, Type>::type; +} + +template<typename M> +static constexpr ArgumentType arg6Type() { + using Type = decltype(&M::call); + return Argument<6, Type>::type; +} + +} // namespace RuntimeSupport +} // namespace IR +} // namespace QV4 + +QT_END_NAMESPACE + +#endif // QV4RUNTIMESUPPORT_P_H diff --git a/src/qml/jit/qv4tracingjit.cpp b/src/qml/jit/qv4tracingjit.cpp new file mode 100644 index 0000000000..ded2488905 --- /dev/null +++ b/src/qml/jit/qv4tracingjit.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qloggingcategory.h> + +#include "qv4vme_moth_p.h" +#include "qv4graphbuilder_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTracing, "qt.v4.tracing") + +namespace QV4 { + +// This is the entry point for the "tracing JIT". It uses the sea-of-nodes concept as described in +// https://scholarship.rice.edu/bitstream/handle/1911/96451/TR95-252.pdf +// +// The minimal pipeline is as follows: +// - create the graph for the function +// - do generic lowering +// - schedule the nodes +// - run minimal stack slot allocation (no re-use of slots) +// - run the assembler +// +// This pipeline has no optimizations, and generates quite inefficient code. It does have the +// advantage that no trace information is used, so it can be used for testing where it replaces +// the baseline JIT. Any optimizations are additions to this pipeline. +// +// Note: generators (or resuming functions in general) are not supported by this JIT. +void Moth::runTracingJit(QV4::Function *function) +{ + IR::Function irFunction(function); + qCDebug(lcTracing).noquote() << "runTracingJit called for" << irFunction.name() << "..."; + + qCDebug(lcTracing).noquote().nospace() << function->traceInfoToString(); + + IR::GraphBuilder::buildGraph(&irFunction); + irFunction.dump(QStringLiteral("initial IR")); + irFunction.verify(); +} + +} // QV4 namespace +QT_END_NAMESPACE |