From cd3ffe7bcfe0830d1e22d9bb6a473ff96b423153 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 22 Apr 2016 10:29:21 +0600 Subject: [PATCH 001/105] Adds BranchNode for easy operation on branches Also branch optimization technique is proposed, however it is currently lead to a malformed graph Issue: #32 --- include/analysis.h | 18 +++++ src/ControlGraph.cpp | 166 ++++++++++++++++++++++++++++++++----------- 2 files changed, 143 insertions(+), 41 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 641f27e..0c101b7 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -194,6 +194,21 @@ class PushBlockNode : public InstructionNode { ParsedBlock* m_parsedBlock; }; +class BranchNode : public InstructionNode { +public: + BranchNode(uint32_t index) : InstructionNode(index), m_targetNode(0), m_skipNode(0) {} + + ControlNode* getTargetNode() const { return m_targetNode; } + ControlNode* getSkipNode() const { return m_skipNode; } + + void setTargetNode(ControlNode* value) { m_targetNode = value; } + void setSkipNode(ControlNode* value) { m_skipNode = value; } + +private: + ControlNode* m_targetNode; + ControlNode* m_skipNode; +}; + // Phi node act as a value aggregator from several domains. // When value is pushed on the stack in one basic block and // popped in another we say that actual values have a stack relation. @@ -433,6 +448,9 @@ template<> TauNode* ControlGraph::newNode(); template<> PushBlockNode* ControlNode::cast(); template<> PushBlockNode* ControlGraph::newNode(); +template<> BranchNode* ControlNode::cast(); +template<> BranchNode* ControlGraph::newNode(); + class DomainVisitor { public: DomainVisitor(ControlGraph* graph) : m_graph(graph) { } diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 522e8fe..4c46497 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -34,11 +34,35 @@ template<> PushBlockNode* ControlNode::cast() { } template<> PushBlockNode* ControlGraph::newNode() { - PushBlockNode* node = new PushBlockNode(m_lastNodeIndex++); + PushBlockNode* const node = new PushBlockNode(m_lastNodeIndex++); m_nodes.push_back(node); return static_cast(node); } +template<> BranchNode* ControlNode::cast() { + if (this->getNodeType() != ntInstruction) + return 0; + + InstructionNode* const node = static_cast(this); + if (node->getInstruction().getOpcode() != opcode::doSpecial) + return 0; + + switch (node->getInstruction().getArgument()) { + case special::branch: + case special::branchIfTrue: + case special::branchIfFalse: + return static_cast(this); + } + + return 0; +} + +template<> BranchNode* ControlGraph::newNode() { + BranchNode* const node = new BranchNode(m_lastNodeIndex++); + m_nodes.push_back(node); + return static_cast(node); +} + TNodeSet PhiNode::getRealValues() { TNodeSet values; @@ -101,10 +125,13 @@ class GraphConstructor : public InstructionVisitor { InstructionNode* GraphConstructor::createNode(const TSmalltalkInstruction& instruction) { + if (instruction.isBranch()) + return m_graph->newNode(); + if (instruction.getOpcode() == opcode::pushBlock) return m_graph->newNode(); - else - return m_graph->newNode(); + + return m_graph->newNode(); } void GraphConstructor::processNode(InstructionNode* node) @@ -321,13 +348,18 @@ void GraphLinker::processBranching() BasicBlock::TBasicBlockSet::iterator iReferer = referers.begin(); for (; iReferer != referers.end(); ++iReferer) { ControlDomain* const refererDomain = m_graph->getDomainFor(*iReferer); - InstructionNode* const terminator = refererDomain->getTerminator(); - assert(terminator && terminator->getInstruction().isBranch()); + BranchNode* const branch = refererDomain->getTerminator()->cast(); + assert(branch); + + if (entryPoint->getDomain()->getBasicBlock()->getOffset() == branch->getInstruction().getExtra()) + branch->setTargetNode(entryPoint); + else + branch->setSkipNode(entryPoint); if (traces_enabled) - std::printf("GraphLinker::processNode : linking nodes of referring graphs %.2u and %.2u\n", terminator->getIndex(), entryPoint->getIndex()); + std::printf("GraphLinker::processNode : linking nodes of referring graphs %.2u and %.2u\n", branch->getIndex(), entryPoint->getIndex()); - terminator->addEdge(entryPoint); + branch->addEdge(entryPoint); } } @@ -474,48 +506,26 @@ class GraphOptimizer : public PlainNodeVisitor { public: GraphOptimizer(ControlGraph* graph) : PlainNodeVisitor(graph) {} - virtual bool visitNode(ControlNode& node) { - // If node pushes value on the stack but this value is not consumed - // by another node, or the only consumer is a popTop instruction - // then we may remove such node (or a node pair) - - if (InstructionNode* const instruction = node.cast()) { - const TSmalltalkInstruction& nodeInstruction = instruction->getInstruction(); - if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) - return true; + bool graphAltered() const { return !m_nodesToRemove.empty(); } - const TNodeSet& consumers = instruction->getConsumers(); - if (consumers.empty()) { - if (traces_enabled) - std::printf("GraphOptimizer::visitNode : node %u is not consumed and may be removed\n", instruction->getIndex()); - - m_nodesToRemove.push_back(instruction); - } else if (consumers.size() == 1) { - if (InstructionNode* const consumer = (*consumers.begin())->cast()) { - const TSmalltalkInstruction& consumerInstruction = consumer->getInstruction(); - if (consumerInstruction == TSmalltalkInstruction(opcode::doSpecial, special::popTop)) { - if (traces_enabled) - std::printf("GraphOptimizer::visitNode : node %u is consumed only by popTop %u and may be removed\n", - instruction->getIndex(), - consumer->getIndex() - ); - - m_nodesToRemove.push_back(consumer); - m_nodesToRemove.push_back(instruction); - } - } - } - } +private: + virtual bool visitNode(ControlNode& node) { +// if (BranchNode* const branch = node.cast()) +// checkBranch(*branch); +// else + if (InstructionNode* const instruction = node.cast()) + checkInstruction(*instruction); return true; } virtual void nodesVisited() { // Removing nodes that were optimized out - TNodeList::iterator iNode = m_nodesToRemove.begin(); + TNodeSet::const_iterator iNode = m_nodesToRemove.begin(); for (; iNode != m_nodesToRemove.end(); ++iNode) { assert((*iNode)->getNodeType() == ControlNode::ntInstruction || (*iNode)->getNodeType() == ControlNode::ntPhi); + if (InstructionNode* const instruction = (*iNode)->cast()) removeInstruction(instruction); else if (PhiNode* const phi = (*iNode)->cast()) @@ -524,6 +534,67 @@ class GraphOptimizer : public PlainNodeVisitor { } private: + void checkBranch(const BranchNode& branch) { + // If branch is targets to an unconditional branch, latter may be removed + + if (BranchNode* const target_branch = branch.getTargetNode()->cast()) { + if (! target_branch->getSkipNode()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is branch to unconditional branch %u and latter may be removed\n", + branch.getIndex(), target_branch->getIndex()); + + m_nodesToRemove.insert(target_branch); + return; + } + } + + if (!branch.getSkipNode()) + return; + + if (BranchNode* const target_branch = branch.getSkipNode()->cast()) { + if (! target_branch->getSkipNode()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is branch to unconditional branch %u and latter may be removed\n", + branch.getIndex(), target_branch->getIndex()); + + m_nodesToRemove.insert(target_branch); + return; + } + } + } + + void checkInstruction(InstructionNode& instruction) { + // If node pushes value on the stack but this value is not consumed + // by another node, or the only consumer is a popTop instruction + // then we may remove such node (or a node pair) + + const TSmalltalkInstruction& nodeInstruction = instruction.getInstruction(); + if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) + return; + + const TNodeSet& consumers = instruction.getConsumers(); + if (consumers.empty()) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is not consumed and may be removed\n", instruction.getIndex()); + + m_nodesToRemove.insert(&instruction); + } else if (consumers.size() == 1) { + if (InstructionNode* const consumer = (*consumers.begin())->cast()) { + const TSmalltalkInstruction& consumerInstruction = consumer->getInstruction(); + if (consumerInstruction == TSmalltalkInstruction(opcode::doSpecial, special::popTop)) { + if (traces_enabled) + std::printf("GraphOptimizer::visitNode : node %u is consumed only by popTop %u and may be removed\n", + instruction.getIndex(), + consumer->getIndex() + ); + + m_nodesToRemove.insert(consumer); + m_nodesToRemove.insert(&instruction); + } + } + } + } + void removePhi(PhiNode* phi) { assert(phi->getInEdges().size() == 1); @@ -563,7 +634,7 @@ class GraphOptimizer : public PlainNodeVisitor { domain->setEntryPoint(nextNode->cast()); // Fixing incoming edges by remapping them to the next node - TNodeSet::iterator iNode = node->getInEdges().begin(); + TNodeSet::const_iterator iNode = node->getInEdges().begin(); while (iNode != node->getInEdges().end()) { ControlNode* const sourceNode = *iNode++; @@ -574,6 +645,19 @@ class GraphOptimizer : public PlainNodeVisitor { nextNode->getIndex() ); + if (BranchNode* const branch = sourceNode->cast()) { + if (traces_enabled) + std::printf("Patching branch info %.2u -> %.2u\n", + sourceNode->getIndex(), + nextNode->getIndex() + ); + + if (branch->getTargetNode() == node) + branch->setTargetNode(nextNode); + else + branch->setSkipNode(nextNode); + } + sourceNode->removeEdge(node); sourceNode->addEdge(nextNode); } @@ -599,7 +683,7 @@ class GraphOptimizer : public PlainNodeVisitor { } private: - TNodeList m_nodesToRemove; + TNodeSet m_nodesToRemove; }; void ControlGraph::buildGraph() From 0e68d06c59dbf88f9b817a6891745ae8a6af3a8e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 22 Apr 2016 10:32:56 +0600 Subject: [PATCH 002/105] Fixes branch node visualization Issue: #32 --- src/ControlGraphVisualizer.cpp | 39 +++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 6af5702..7def112 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -99,17 +99,16 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { if (isNodeProcessed(*iEdge)) continue; + if (const st::InstructionNode* const instruction = (*iEdge)->cast()) { + // Branch edges are handled separately + if (instruction->getInstruction().isBranch()) + continue; + } + m_stream << "\t\t" << (*iEdge)->getIndex() << " -> " << node.getIndex() << edgeStyle(*iEdge, &node) << ";\n"; } - // Processing outgoing edges - iEdge = outEdges.begin(); - for (; iEdge != outEdges.end(); ++iEdge) { - if (isNodeProcessed(*iEdge)) - continue; - - m_stream << "\t\t" << node.getIndex() << " -> " << (*iEdge)->getIndex() << edgeStyle(&node, *iEdge) << ";\n"; - } + bool outEdgesProcessed = false; // Processing argument edges if (const st::InstructionNode* const instruction = node.cast()) { @@ -122,6 +121,19 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { m_stream << " labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=false];\n"; } + + if (const st::BranchNode* const branch = node.cast()) { + m_stream << "\t\t" << node.getIndex() << " -> " << branch->getTargetNode()->getIndex() << " ["; + m_stream << "label=target labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + + if (branch->getSkipNode()) { + m_stream << "\t\t" << node.getIndex() << " -> " << branch->getSkipNode()->getIndex() << " ["; + m_stream << "label=skip labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + } + + outEdgesProcessed = true; + } + } else if (const st::PhiNode* const phi = node.cast()) { m_stream << "\t\t" << phi->getIndex() << " -> " << phi->getDomain()->getEntryPoint()->getIndex() << " [" @@ -139,6 +151,17 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { } } + // Processing outgoing edges in generic way + if (!outEdgesProcessed) { + iEdge = outEdges.begin(); + for (; iEdge != outEdges.end(); ++iEdge) { + if (isNodeProcessed(*iEdge)) + continue; + + m_stream << "\t\t" << node.getIndex() << " -> " << (*iEdge)->getIndex() << edgeStyle(&node, *iEdge) << ";\n"; + } + } + markNode(&node); return st::PlainNodeVisitor::visitNode(node); } From 113bed0202517a1fad9a362a67081a57c4686fd6 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 28 Apr 2016 20:42:04 +0600 Subject: [PATCH 003/105] Fixes type.txt --- doc/types.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/types.txt b/doc/types.txt index 176c12d..4b595ee 100644 --- a/doc/types.txt +++ b/doc/types.txt @@ -17,7 +17,7 @@ id: param ^param -Ти́повое выражение: +Т́иповое выражение: self::*, param::* -> self, param @@ -26,13 +26,13 @@ ^ a + b Контекст object sum: intvara and: intvarb - self::*, (SmallInt), (SmallInt) -> self, (SmallInt) + self::*, a::(SmallInt), b::(SmallInt) -> self', (SmallInt) Контекст object sum: 2 and: 3 self::*, a::2, b::3 -> self, 5 Обобщенный метод - self::*, a::*, b::* -> self'::*, * + self::*, a::*, b::* -> self', * то есть, литеральные значения протаскиваются прямо через тип @@ -72,9 +72,10 @@ Контекст object dirty: 42 self::[_,_,_], param::42 -> self'::[_,_,42], 43 +Примечание: в случае утекания self, мы не можем утверждать, что self' будет таким. Придется фолбечиться на self'::* -А теперь самый вынос мозга — система контекста виртуальной машине — это монод в категории эндофункторов! Монада то бишь. +А теперь самый вынос мозга — система контекста виртуальной машине — это моноид в категории эндофункторов! Монада то бишь. Переход виртуальной машины от одного состояния к другому — это монадическая операция. А это внезапно означает: @@ -106,6 +107,7 @@ Монадические отношения в связи с объектом self позволяют формализовать тот факт, что при вызове метода поля самого объекта не меняются. Это позволяет более смело выполнять оптимизации и связывать далекие участки графа без опасения что типы совпали «случайно». +Поправка — нихрена :( Если self утек в другой объект, то вызывая методы он может поменять и нас. Только при инлайнинге и доказательсве. Типы методов: From 1688a7d57607f4adf014b6b6c2e831a07cecd22e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 29 Apr 2016 00:08:31 +0600 Subject: [PATCH 004/105] Adds stub for TauLinker --- include/analysis.h | 13 ++++++++- src/ControlGraph.cpp | 66 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 0c101b7..5c9f817 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -459,6 +459,8 @@ class DomainVisitor { virtual bool visitDomain(ControlDomain& /*domain*/) { return true; } virtual void domainsVisited() { } + ControlGraph& getGraph(); + void run() { ControlGraph::iterator iDomain = m_graph->begin(); const ControlGraph::iterator iEnd = m_graph->end(); @@ -568,7 +570,7 @@ class GraphWalker { const TNodeSet& nodes = (m_direction == wdForward) ? currentNode->getOutEdges() : currentNode->getInEdges(); - for (TNodeSet::iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) { + for (TNodeSet::const_iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) { ControlNode* const node = *iNode; if (m_stopNodes.find(node) != m_stopNodes.end()) @@ -601,8 +603,17 @@ class GraphWalker { class ForwardWalker : public GraphWalker { public: void run(ControlNode* startNode) { GraphWalker::run(startNode, wdForward); } + +private: + void run(ControlNode* startNode, TWalkDirection direction); }; +class BackwardWalker : public GraphWalker { +public: + void run(ControlNode* startNode) { GraphWalker::run(startNode, wdBackward); } +}; + + class PathVerifier : public ForwardWalker { public: PathVerifier(const TNodeSet& destinationNodes) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 4c46497..bdddf81 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -686,14 +686,64 @@ class GraphOptimizer : public PlainNodeVisitor { TNodeSet m_nodesToRemove; }; +class TauLinker : public NodeVisitor { +public: + TauLinker(ControlGraph* graph) : NodeVisitor(graph) {} + +private: + virtual bool visitNode(st::ControlNode& node) { + if (InstructionNode* const instruction = node.cast()) { + if (instruction->getInstruction().getOpcode() == opcode::pushTemporary) + processPushTemporary(*instruction); + } + + return true; + } + + void processPushTemporary(InstructionNode& instruction) { + // Searching for all AssignTemporary's that provide a value for current node + AssignLocator locator(instruction.getInstruction().getArgument()); + locator.run(&instruction); + + TNodeList::const_iterator iNode = locator.assign_sites.begin(); + for (; iNode != locator.assign_sites.end(); ++iNode) { + std::printf("Node %.2u is affected by node %.2u\n", + instruction.getIndex(), (*iNode)->getIndex()); + } + } + + class AssignLocator : public BackwardWalker { + public: + AssignLocator(TSmalltalkInstruction::TArgument argument) : argument(argument) {} + + virtual TVisitResult visitNode(st::ControlNode* node) { + if (const InstructionNode* const instruction = node->cast()) { + if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + if (instruction->getInstruction().getArgument() == argument) { + assign_sites.push_back(node); + return vrSkipPath; + } + } + } + + return vrKeepWalking; + } + + const TSmalltalkInstruction::TArgument argument; + TNodeList assign_sites; + }; +}; + void ControlGraph::buildGraph() { if (traces_enabled) std::printf("Phase 1. Constructing control graph\n"); // Iterating through basic blocks of parsed method and constructing node domains - GraphConstructor constructor(this); - constructor.run(); + { + GraphConstructor constructor(this); + constructor.run(); + } if (traces_enabled) std::printf("Phase 2. Linking control graph\n"); @@ -702,8 +752,10 @@ void ControlGraph::buildGraph() // They're linked using phi nodes or a direct link if possible. // Also branching edges are added so graph remains linked even if // no stack relations exist. - GraphLinker linker(this); - linker.run(); + { + GraphLinker linker(this); + linker.run(); + } if (traces_enabled) std::printf("Phase 3. Optimizing control graph\n"); @@ -711,4 +763,10 @@ void ControlGraph::buildGraph() // Optimizing graph by removing stalled nodes and merging linear branch sequences GraphOptimizer optimizer(this); optimizer.run(); + + // Linking PushTemporary and AssignTemporary pairs + { + TauLinker linker(this); + linker.run(); + } } From 90b42b2e613a56086c0ba604de4c546d0886fc96 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 2 May 2016 13:59:20 +0600 Subject: [PATCH 005/105] Refactors GraphWalker to use node colors This allows us to classify graph edges during graph walk. --- include/analysis.h | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 5c9f817..f4c775f 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -236,7 +236,7 @@ class PhiNode : public ControlNode { void addIncoming(ControlDomain* domain, ControlNode* value) { TIncoming incoming; incoming.domain = domain; - incoming.node = value; + incoming.node = value; m_incomingList.push_back(incoming); } @@ -538,9 +538,14 @@ class GraphWalker { GraphWalker() { } virtual ~GraphWalker() { } - void addStopNode(ControlNode* node) { m_stopNodes.insert(node); } - void addStopNodes(const TNodeSet& nodes) { m_stopNodes.insert(nodes.begin(), nodes.end()); } - void resetStopNodes() { m_stopNodes.clear(); } + void resetStopNodes() { m_colorMap.clear(); } + void addStopNode(ControlNode* node) { m_colorMap[node] = ncBlack; } + + void addStopNodes(const TNodeSet& nodes) { + TNodeSet::const_iterator iNode = nodes.begin(); + for (; iNode != nodes.end(); ++iNode) + m_colorMap[*iNode] = ncBlack; + } enum TVisitResult { vrKeepWalking = 0, @@ -560,23 +565,39 @@ class GraphWalker { assert(startNode); m_direction = direction; - m_stopNodes.erase(startNode); walkIn(startNode); nodesVisited(); } +protected: + enum TNodeColor { + ncWhite = 0, // unvisited node + ncGrey, // node in progress + ncBlack // visited and settled node + }; + + typedef std::map TColorMap; + + TNodeColor getNodeColor(ControlNode* node) const { + TColorMap::const_iterator iColor = m_colorMap.find(node); + if (iColor != m_colorMap.end()) + return iColor->second; + else + return ncWhite; + } + private: bool walkIn(ControlNode* currentNode) { + m_colorMap[currentNode] = ncGrey; + const TNodeSet& nodes = (m_direction == wdForward) ? currentNode->getOutEdges() : currentNode->getInEdges(); for (TNodeSet::const_iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) { ControlNode* const node = *iNode; - if (m_stopNodes.find(node) != m_stopNodes.end()) + if (getNodeColor(node) != ncWhite) continue; - else - m_stopNodes.insert(node); switch (const TVisitResult result = visitNode(node)) { case vrKeepWalking: @@ -592,12 +613,13 @@ class GraphWalker { } } + m_colorMap[currentNode] = ncBlack; return true; } private: TWalkDirection m_direction; - TNodeSet m_stopNodes; + TColorMap m_colorMap; }; class ForwardWalker : public GraphWalker { From 1dba10d4055b7206025a56735b9dacbce83fe987 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 2 May 2016 14:01:40 +0600 Subject: [PATCH 006/105] Adds sample back edge classifier --- include/analysis.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/include/analysis.h b/include/analysis.h index f4c775f..e65ee44 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -669,7 +669,55 @@ class PathVerifier : public ForwardWalker { bool m_verified; }; +class BackEdgeDetector { +public: + struct TEdge { + InstructionNode* from; + InstructionNode* to; + + TEdge(InstructionNode* from, InstructionNode* to) + : from(from), to(to) + { + assert(from); + assert(to); + } + }; + + typedef std::list TEdgeList; + + const TEdgeList& getBackEdges() const { return m_backEdges; } + + void run(ControlGraph& graph) { + m_backEdges.clear(); + + Walker walker(*this); + walker.run(*graph.nodes_begin(), GraphWalker::wdForward); + } + +private: + class Walker : public GraphWalker { + public: + Walker(BackEdgeDetector& detector) : detector(detector) {} + virtual TVisitResult visitNode(ControlNode* node) { + if (BranchNode* const branch = node->cast()) { + InstructionNode* const target = branch->getTargetNode()->cast(); + assert(target); + + if (getNodeColor(target) == ncGrey) + detector.m_backEdges.push_back(TEdge(branch, target)); + } + + return vrKeepWalking; + } + + private: + BackEdgeDetector& detector; + }; + +private: + TEdgeList m_backEdges; +}; } // namespace st From b77dd54e4ef49f16d5a9897f640b069eb8b8d232 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 2 May 2016 14:02:44 +0600 Subject: [PATCH 007/105] Minor fixes in GraphLinker --- src/ControlGraph.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index bdddf81..d02c176 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -569,8 +569,10 @@ class GraphOptimizer : public PlainNodeVisitor { // then we may remove such node (or a node pair) const TSmalltalkInstruction& nodeInstruction = instruction.getInstruction(); - if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) - return; + if (!nodeInstruction.isTrivial() || !nodeInstruction.isValueProvider()) { + if (! instruction.cast()) + return; + } const TNodeSet& consumers = instruction.getConsumers(); if (consumers.empty()) { @@ -588,6 +590,7 @@ class GraphOptimizer : public PlainNodeVisitor { consumer->getIndex() ); + // TODO Remove phi incoming and probably remove phi node along with it's consumer m_nodesToRemove.insert(consumer); m_nodesToRemove.insert(&instruction); } @@ -633,6 +636,8 @@ class GraphOptimizer : public PlainNodeVisitor { if (domain->getEntryPoint() == node) domain->setEntryPoint(nextNode->cast()); + // TODO Phi node + // Fixing incoming edges by remapping them to the next node TNodeSet::const_iterator iNode = node->getInEdges().begin(); while (iNode != node->getInEdges().end()) { From c91b0438d3aba24a254b85dcceacced0c3d81ed5 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 2 May 2016 14:12:42 +0600 Subject: [PATCH 008/105] Removes Forward- and Backward- Walkers as violating the LSP --- include/analysis.h | 18 ++---------------- src/ControlGraph.cpp | 4 ++-- src/MethodCompiler.cpp | 4 ++-- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index e65ee44..7460e20 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -622,21 +622,7 @@ class GraphWalker { TColorMap m_colorMap; }; -class ForwardWalker : public GraphWalker { -public: - void run(ControlNode* startNode) { GraphWalker::run(startNode, wdForward); } - -private: - void run(ControlNode* startNode, TWalkDirection direction); -}; - -class BackwardWalker : public GraphWalker { -public: - void run(ControlNode* startNode) { GraphWalker::run(startNode, wdBackward); } -}; - - -class PathVerifier : public ForwardWalker { +class PathVerifier : public GraphWalker { public: PathVerifier(const TNodeSet& destinationNodes) : m_destinationNodes(destinationNodes), m_verified(false) {} @@ -648,7 +634,7 @@ class PathVerifier : public ForwardWalker { assert(startNode); m_verified = false; - ForwardWalker::run(startNode); + GraphWalker::run(startNode, wdForward); } private: diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index d02c176..bb9b62b 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -708,7 +708,7 @@ class TauLinker : public NodeVisitor { void processPushTemporary(InstructionNode& instruction) { // Searching for all AssignTemporary's that provide a value for current node AssignLocator locator(instruction.getInstruction().getArgument()); - locator.run(&instruction); + locator.run(&instruction, GraphWalker::wdBackward); TNodeList::const_iterator iNode = locator.assign_sites.begin(); for (; iNode != locator.assign_sites.end(); ++iNode) { @@ -717,7 +717,7 @@ class TauLinker : public NodeVisitor { } } - class AssignLocator : public BackwardWalker { + class AssignLocator : public GraphWalker { public: AssignLocator(TSmalltalkInstruction::TArgument argument) : argument(argument) {} diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 456afb9..18806ce 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -181,7 +181,7 @@ class Walker : public st::GraphWalker { bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) { - class GCDetector : public st::ForwardWalker { + class GCDetector : public st::GraphWalker { public: GCDetector() : m_detected(false) {} @@ -203,7 +203,7 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) }; GCDetector detector; - detector.run((*jit.controlGraph->begin())->getEntryPoint()); + detector.run((*jit.controlGraph->begin())->getEntryPoint(), GCDetector::wdForward); return detector.isDetected(); } From 8dcb48fd582bcd7c42a6bd4815b291f78965cbbc Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 6 May 2016 01:01:57 +0600 Subject: [PATCH 009/105] Adds tau node linkage and optimization logic --- include/analysis.h | 45 +++++-- src/ControlGraph.cpp | 293 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 314 insertions(+), 24 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 7460e20..40d3b26 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -127,7 +127,7 @@ class ControlNode { void setValue(llvm::Value* value) { m_value = value; } llvm::Value* getValue() const { return m_value; } - // Get a list of nodes which refer current node as argument + // Get a list of nodes which refer current node as argument and tau nodes void addConsumer(ControlNode* consumer) { m_consumers.insert(consumer); } void removeConsumer(ControlNode* consumer) { m_consumers.erase(consumer); } const TNodeSet& getConsumers() const { return m_consumers; } @@ -143,10 +143,12 @@ class ControlNode { TNodeSet m_consumers; }; +class TauNode; + // Instruction node represents a signle VM instruction and it's relations in code. class InstructionNode : public ControlNode { public: - InstructionNode(uint32_t index) : ControlNode(index), m_instruction(opcode::extended) { } + InstructionNode(uint32_t index) : ControlNode(index), m_instruction(opcode::extended), m_tau(0) { } virtual TNodeType getNodeType() const { return ntInstruction; } void setInstruction(TSmalltalkInstruction instruction) { m_instruction = instruction; } @@ -175,9 +177,13 @@ class InstructionNode : public ControlNode { iterator begin() { return m_arguments.begin(); } iterator end() { return m_arguments.end(); } + TauNode* getTauNode() const { return m_tau; } + void setTauNode(TauNode* value) { m_tau = value; } + private: TSmalltalkInstruction m_instruction; TArgumentList m_arguments; + TauNode* m_tau; }; // PushBlockNode represents a single PushBlock instruction. @@ -257,8 +263,28 @@ class PhiNode : public ControlNode { // It will link variable type transitions across a method. class TauNode : public ControlNode { public: - TauNode(uint32_t index) : ControlNode(index) { } + TauNode(uint32_t index) : ControlNode(index), m_kind(tkUnknown) { } virtual TNodeType getNodeType() const { return ntTau; } + + void addIncoming(ControlNode* node) { + m_incomingSet.insert(node); + node->addConsumer(this); + } + + const TNodeSet& getIncomingSet() const { return m_incomingSet; } + + enum TKind { + tkUnknown, + tkProvider, + tkAggregator + }; + + void setKind(TKind value) { m_kind = value; } + TKind getKind() const { return m_kind; } + +private: + TNodeSet m_incomingSet; + TKind m_kind; }; // Domain is a group of nodes within a graph @@ -288,9 +314,10 @@ class ControlDomain { ControlNode* topValue(bool keep = false) { assert(! m_localStack.empty()); - ControlNode* value = m_localStack.back(); + ControlNode* const value = m_localStack.back(); if (!keep) m_localStack.pop_back(); + return value; } @@ -459,7 +486,7 @@ class DomainVisitor { virtual bool visitDomain(ControlDomain& /*domain*/) { return true; } virtual void domainsVisited() { } - ControlGraph& getGraph(); + ControlGraph& getGraph() { return *m_graph; } void run() { ControlGraph::iterator iDomain = m_graph->begin(); @@ -477,7 +504,7 @@ class DomainVisitor { } } -protected: +private: ControlGraph* const m_graph; }; @@ -676,8 +703,10 @@ class BackEdgeDetector { void run(ControlGraph& graph) { m_backEdges.clear(); - Walker walker(*this); - walker.run(*graph.nodes_begin(), GraphWalker::wdForward); + if (graph.nodes_begin() == graph.nodes_end()) + return; + + Walker(*this).run(*graph.nodes_begin(), GraphWalker::wdForward); } private: diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index bb9b62b..038df4b 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -347,7 +347,7 @@ void GraphLinker::processBranching() const BasicBlock::TBasicBlockSet& referers = m_currentDomain->getBasicBlock()->getReferers(); BasicBlock::TBasicBlockSet::iterator iReferer = referers.begin(); for (; iReferer != referers.end(); ++iReferer) { - ControlDomain* const refererDomain = m_graph->getDomainFor(*iReferer); + ControlDomain* const refererDomain = getGraph().getDomainFor(*iReferer); BranchNode* const branch = refererDomain->getTerminator()->cast(); assert(branch); @@ -400,7 +400,7 @@ void GraphLinker::mergePhi(PhiNode* source, PhiNode* target) } // Deleting source node because it is no longer used - m_graph->eraseNode(source); + getGraph().eraseNode(source); } ControlNode* GraphLinker::optimizePhi(PhiNode* phi) @@ -435,7 +435,7 @@ ControlNode* GraphLinker::optimizePhi(PhiNode* phi) // Unlink and erase phi value->removeConsumer(phi); value->removeEdge(phi); - m_graph->eraseNode(phi); + getGraph().eraseNode(phi); return value; } @@ -450,14 +450,14 @@ ControlNode* GraphLinker::getRequestedNode(ControlDomain* domain, std::size_t ar ControlNode* result = 0; if (!singleReferer) { - PhiNode* const phi = m_graph->newNode(); + PhiNode* const phi = getGraph().newNode(); phi->setDomain(domain); result = phi; } BasicBlock::TBasicBlockSet::iterator iBlock = refererBlocks.begin(); for (; iBlock != refererBlocks.end(); ++iBlock) { - ControlDomain* const refererDomain = m_graph->getDomainFor(* iBlock); + ControlDomain* const refererDomain = getGraph().getDomainFor(* iBlock); const TNodeList& refererStack = refererDomain->getLocalStack(); const std::size_t refererStackSize = refererStack.size(); @@ -695,25 +695,270 @@ class TauLinker : public NodeVisitor { public: TauLinker(ControlGraph* graph) : NodeVisitor(graph) {} +private: + typedef std::set TInstructionSet; + TInstructionSet m_pendingNodes; + + typedef std::list TTauList; + TTauList m_providers; + private: virtual bool visitNode(st::ControlNode& node) { if (InstructionNode* const instruction = node.cast()) { - if (instruction->getInstruction().getOpcode() == opcode::pushTemporary) - processPushTemporary(*instruction); + switch (instruction->getInstruction().getOpcode()) { + case opcode::pushTemporary: + m_pendingNodes.insert(instruction); + break; + +// case opcode::pushConstant: +// case opcode::pushLiteral: + case opcode::assignTemporary: + createType(*instruction); + break; + +// case opcode::pushArgument: +// case opcode::sendUnary: +// case opcode::sendBinary: +// case opcode::sendMessage: +// createType(*instruction); +// break; + + case opcode::doSpecial: + switch (instruction->getInstruction().getArgument()) { +// case special::duplicate: +// inheritType(); +// break; + + case special::sendToSuper: + createType(*instruction); + break; + } + break; + + default: + break; + } } return true; } + virtual void domainsVisited() { + // When all nodes visited, process the pending list + TInstructionSet::iterator iNode = m_pendingNodes.begin(); + for (; iNode != m_pendingNodes.end(); ++iNode) + processPushTemporary(**iNode); + + optimizeTau(); + } + +private: + typedef std::set TTauSet; + typedef std::map TRedundantTauMap; + TRedundantTauMap m_redundantTaus; + TTauSet m_processedTaus; + +private: + void optimizeTau() { + detectRedundantTau(); + eraseRedundantTau(); + } + + void eraseRedundantTau() { + TRedundantTauMap::iterator iProvider = m_redundantTaus.begin(); + for (; iProvider != m_redundantTaus.end(); ++iProvider) { + printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); + + TTauSet& pendingTaus = iProvider->second; + TTauSet::iterator iPendingTau = pendingTaus.begin(); + + // Skipping nodes that were processed earlier + while (iPendingTau != pendingTaus.end()) { + if (m_processedTaus.find(*iPendingTau) != m_processedTaus.end()) { + printf("Redundant tau %.2u was already processed earlier\n", + (*iPendingTau)->getIndex()); + + TTauSet::iterator iCurrent = iPendingTau++; + pendingTaus.erase(*iCurrent); + } else { + ++iPendingTau; + } + } + + if (pendingTaus.size() < 2) { + printf("Nothing to be done for provider tau %.2u\n", (*iProvider).first->getIndex()); + continue; + } + + iPendingTau = pendingTaus.begin(); + TauNode* const remainingTau = *iPendingTau++; + + for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { + TauNode* const redundantTau = *iPendingTau; + const TNodeSet& consumers = redundantTau->getConsumers(); + +// printf("Remapping consumers of redundant tau %.2u to remaining tau %.2u\n", +// (*iRedundantTau)->getIndex(), remainingTau->getIndex()); + + // Remap all consumers to the remainingTau + TNodeSet::iterator iConsumer = consumers.begin(); + for ( ; iConsumer != consumers.end(); ++iConsumer) { + // FIXME Could there be non-instruction nodes? + if (InstructionNode* const instruction = (*iConsumer)->cast()) { + printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", + instruction->getIndex(), + redundantTau->getIndex(), + remainingTau->getIndex()); + + instruction->setTauNode(remainingTau); + remainingTau->addConsumer(instruction); + } + } + + // Remove all incomings of the redundantTau + TNodeSet::iterator iIncoming = redundantTau->getIncomingSet().begin(); + for ( ; iIncoming != redundantTau->getIncomingSet().end(); ++iIncoming) { + printf("Redundant tau %.2u is no longer consumer of %.2u\n", + redundantTau->getIndex(), + (*iIncoming)->getIndex()); + + (*iIncoming)->removeConsumer(redundantTau); + } + + // Marking tau as processed + m_processedTaus.insert(redundantTau); + printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); + } + } + + m_redundantTaus.clear(); + + // Erasing all redundant taus completely + TTauSet::const_iterator iProcessedTau = m_processedTaus.begin(); + for (; iProcessedTau != m_processedTaus.end(); ++iProcessedTau) { + TauNode* const processedTau = *iProcessedTau; + + printf("Erasing processed tau %.2u\n", processedTau->getIndex()); + assert(processedTau->consumers.empty()); + getGraph().eraseNode(processedTau); + } + } + + void detectRedundantTau() { + TTauList::const_iterator iProvider = m_providers.begin(); + for (; iProvider != m_providers.end(); ++iProvider) { + const TNodeSet& consumers = (*iProvider)->getConsumers(); + if (consumers.size() < 2) + continue; + + printf("Looking for consumers of Tau %.2u (total %zu)\n", + (*iProvider)->getIndex(), consumers.size()); + + TNodeSet::iterator iConsumer1 = consumers.begin(); + for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { + TauNode* const tau1 = (*iConsumer1)->cast(); + if (! tau1) + continue; + +// printf("Tau1 is %.2u\n", tau1->getIndex()); + + TNodeSet::iterator iConsumer2 = iConsumer1; + ++iConsumer2; + + for (; iConsumer2 != consumers.end(); ++iConsumer2) { + TauNode* const tau2 = (*iConsumer2)->cast(); + if (!tau2) + continue; + +// printf("Tau2 is %.2u\n", tau2->getIndex()); + + if (tau1->getIncomingSet() == tau2->getIncomingSet()) { + printf("Tau %.2u and %.2u may be optimized\n", + tau1->getIndex(), tau2->getIndex()); + + m_redundantTaus[*iProvider].insert(tau1); + m_redundantTaus[*iProvider].insert(tau2); + } + } + } + } + } + + void createType(InstructionNode& instruction) { + TauNode* const tau = getGraph().newNode(); + tau->setKind(TauNode::tkProvider); + tau->addIncoming(&instruction); + instruction.setTauNode(tau); + + m_providers.push_back(tau); + + std::printf("New type: Node %u.%.2u --> Tau %.2u\n", + instruction.getDomain()->getBasicBlock()->getOffset(), + instruction.getIndex(), + tau->getIndex()); + } + void processPushTemporary(InstructionNode& instruction) { // Searching for all AssignTemporary's that provide a value for current node AssignLocator locator(instruction.getInstruction().getArgument()); locator.run(&instruction, GraphWalker::wdBackward); - TNodeList::const_iterator iNode = locator.assign_sites.begin(); - for (; iNode != locator.assign_sites.end(); ++iNode) { - std::printf("Node %.2u is affected by node %.2u\n", - instruction.getIndex(), (*iNode)->getIndex()); + TInstructionSet::const_iterator iNode = locator.assignSites.begin(); + for (; iNode != locator.assignSites.end(); ++iNode) { +// std::printf("Node %.2u is affected by node %.2u\n", +// instruction.getIndex(), (*iNode)->getIndex()); + + InstructionNode* const assignTemporary = (*iNode)->cast(); + assert(assignTemporary); + + InstructionNode* const argument = assignTemporary->getArgument()->cast(); + if (!argument) + continue; + + TauNode* const inheritedType = assignTemporary->getTauNode(); //argument->getTauNode(); + assert(inheritedType); + + // FIXME Inherit type from argument + + if (! instruction.getTauNode()) { + inheritedType->addConsumer(&instruction); + instruction.setTauNode(inheritedType); + + std::printf("Inherit type: Tau %.2u <-- %.2u\n", + inheritedType->getIndex(), + instruction.getIndex()); + + } else { + TauNode* const current = instruction.getTauNode(); + + if (current->getKind() == TauNode::tkProvider) { + current->removeConsumer(&instruction); + + TauNode* const aggregator = getGraph().newNode(); + aggregator->setKind(TauNode::tkAggregator); + aggregator->addIncoming(current); + aggregator->addIncoming(inheritedType); + + aggregator->addConsumer(&instruction); + instruction.setTauNode(aggregator); + + std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u\n", + instruction.getIndex(), + current->getIndex(), + aggregator->getIndex()); + + } else { + current->addIncoming(inheritedType); + + std::printf("Attached to existing tau: Node %.2u --> Tau %.2u\n", + instruction.getIndex(), + current->getIndex()); + } + +// std::printf("Inherit type: Tau %.2u <-- %.2u\n", +// inheritedType->getIndex(), +// instruction.getIndex()); + } } } @@ -722,10 +967,10 @@ class TauLinker : public NodeVisitor { AssignLocator(TSmalltalkInstruction::TArgument argument) : argument(argument) {} virtual TVisitResult visitNode(st::ControlNode* node) { - if (const InstructionNode* const instruction = node->cast()) { + if (InstructionNode* const instruction = node->cast()) { if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { if (instruction->getInstruction().getArgument() == argument) { - assign_sites.push_back(node); + assignSites.insert(instruction); return vrSkipPath; } } @@ -735,7 +980,7 @@ class TauLinker : public NodeVisitor { } const TSmalltalkInstruction::TArgument argument; - TNodeList assign_sites; + TInstructionSet assignSites; }; }; @@ -766,12 +1011,28 @@ void ControlGraph::buildGraph() std::printf("Phase 3. Optimizing control graph\n"); // Optimizing graph by removing stalled nodes and merging linear branch sequences - GraphOptimizer optimizer(this); - optimizer.run(); + { + GraphOptimizer optimizer(this); + optimizer.run(); + } // Linking PushTemporary and AssignTemporary pairs { TauLinker linker(this); linker.run(); } + + { + BackEdgeDetector detector; + detector.run(*this); + + BackEdgeDetector::TEdgeList::const_iterator iEdge = detector.getBackEdges().begin(); + for (; iEdge != detector.getBackEdges().end(); ++iEdge) { + const BackEdgeDetector::TEdge& edge = *iEdge; + + std::printf("Back edge %u.%.2u -> %u.%.2u\n", + edge.from->getDomain()->getBasicBlock()->getOffset(), edge.from->getIndex(), + edge.to->getDomain()->getBasicBlock()->getOffset(), edge.to->getIndex()); + } + } } From d4a7be72ccd87a1e585788dcaa860511932f21ee Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 6 May 2016 01:02:51 +0600 Subject: [PATCH 010/105] Adds logic to render tau nodes --- src/ControlGraphVisualizer.cpp | 79 ++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 7def112..410c922 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -74,17 +74,20 @@ bool ControlGraphVisualizer::visitDomain(st::ControlDomain& /*domain*/) { } std::string edgeStyle(st::ControlNode* from, st::ControlNode* to) { - const st::InstructionNode* const fromInstruction = from->cast(); - const st::InstructionNode* const toInstruction = to->cast(); +// const st::InstructionNode* const fromInstruction = from->cast(); + const st::InstructionNode* const toInstruction = to->cast(); if (from->getNodeType() == st::ControlNode::ntPhi && to->getNodeType() == st::ControlNode::ntPhi) return "[style=invis color=red constraint=false]"; - if (fromInstruction && fromInstruction->getInstruction().isBranch()) - return "[color=\"grey\" style=\"dashed\"]"; +// if (from->getNodeType() == st::ControlNode::ntTau && to->getNodeType() == st::ControlNode::ntTau) +// return "[style=invis color=red constraint=false]"; + +// if (fromInstruction && fromInstruction->getInstruction().isBranch()) +// return "[color=\"grey\" style=\"dashed\"]"; if (toInstruction && toInstruction->getArgumentsCount() == 0) - return "[color=\"black\" style=\"dashed\" ]"; + return "[weight=100 color=\"black\" style=\"dashed\" ]"; return ""; } @@ -114,26 +117,53 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { if (const st::InstructionNode* const instruction = node.cast()) { const std::size_t argsCount = instruction->getArgumentsCount(); for (std::size_t index = 0; index < argsCount; index++) { - m_stream << "\t\t" << node.getIndex() << " -> " << instruction->getArgument(index)->getIndex() << " ["; + m_stream << "\t\t" << instruction->getArgument(index)->getIndex() << " -> " << node.getIndex() << " ["; if (argsCount > 1) m_stream << "label=" << index; - m_stream << " labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=false];\n"; + m_stream << "dir=back weight=8 labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=true];\n"; + + + /*if (const st::InstructionNode* const instructionArg = instruction->getArgument(index)->cast()) { + if (const st::TauNode* const argumentTau = instructionArg->getTauNode()) { + m_stream << "\t\t" << argumentTau->getIndex() << " -> " << instructionArg->getIndex() << " [" + << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" " + << "constraint=false ];\n"; + } + }*/ + } if (const st::BranchNode* const branch = node.cast()) { m_stream << "\t\t" << node.getIndex() << " -> " << branch->getTargetNode()->getIndex() << " ["; - m_stream << "label=target labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + m_stream << "weight=20 label=target labelfloat=true color=\"grey\" style=\"dashed\"];\n"; if (branch->getSkipNode()) { m_stream << "\t\t" << node.getIndex() << " -> " << branch->getSkipNode()->getIndex() << " ["; - m_stream << "label=skip labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + m_stream << "weight=20 label=skip labelfloat=true color=\"grey\" style=\"dashed\"];\n"; } outEdgesProcessed = true; } + /*if (const st::TauNode* const tau = instruction->getTauNode()) { + const char* constraint = tau->getIncomingSet().size() == 1 ? "true" : "false"; + + m_stream << "\t\t" << instruction->getIndex() << " -> " << tau->getIndex() << " [" + << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" " + << "constraint=" << constraint << " ];\n"; + }*/ + + /*st::TNodeSet::const_iterator iNode = instruction->getConsumers().begin(); + for (; iNode != instruction->getConsumers().end(); ++iNode) { + if ((*iNode)->getNodeType() != st::ControlNode::ntTau) + continue; + + m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" + << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" constraint=false ];\n"; + }*/ + } else if (const st::PhiNode* const phi = node.cast()) { m_stream << "\t\t" << phi->getIndex() << " -> " << phi->getDomain()->getEntryPoint()->getIndex() << " [" @@ -149,6 +179,30 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { m_stream << "\t\t" << incoming.domain->getTerminator()->getIndex() << " -> " << phi->getIndex() << " [" << "style=\"invis\" constraint=true ];\n"; } + } else if (const st::TauNode* const tau = node.cast()) { + + st::TNodeSet::const_iterator iNode = tau->getIncomingSet().begin(); + for (; iNode != tau->getIncomingSet().end(); ++iNode) { +// if ((*iNode)->getNodeType() == st::ControlNode::ntInstruction) +// continue; + + if (tau->getKind() == st::TauNode::tkProvider) { + m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" + << "weight=15 dir=back labelfloat=true color=\"red\" fontcolor=\"red\" style=\"dashed\" constraint=true ];\n"; + } else { + m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" + << "weight=5 dir=back labelfloat=true color=\"grey\" fontcolor=\"green\" style=\"dashed\" constraint=true ];\n"; + } + } + + iNode = tau->getConsumers().begin(); + for (; iNode != tau->getConsumers().end(); ++iNode) { + if ((*iNode)->getNodeType() == st::ControlNode::ntTau) + continue; + + m_stream << "\t\t" << tau->getIndex() << " -> " << (*iNode)->getIndex() << " [" + << "weight=15 dir=back labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" constraint=true ];\n"; + } } // Processing outgoing edges in generic way @@ -179,13 +233,14 @@ void ControlGraphVisualizer::markNode(st::ControlNode* node) { switch (node->getNodeType()) { case st::ControlNode::ntPhi: //label = "Phi "; - color = "grey"; + color = "blue"; shape = "oval"; break; case st::ControlNode::ntTau: label = "Tau "; - color = "green"; + color = (node->cast()->getKind() == st::TauNode::tkProvider) ? "red" : "green"; + shape = "oval"; break; case st::ControlNode::ntInstruction: { @@ -225,7 +280,7 @@ void ControlGraphVisualizer::markNode(st::ControlNode* node) { ; } - if (node->getNodeType() == st::ControlNode::ntPhi) + if (node->getNodeType() == st::ControlNode::ntPhi || node->getNodeType() == st::ControlNode::ntTau) m_stream << "\t\t" << node->getIndex() << " [label=\"" << node->getIndex() << "\" color=\"" << color << "\"];\n"; else m_stream << "\t\t" << node->getIndex() << " [shape=\"" << shape << "\" label=\"" << (node->getDomain() ? node->getDomain()->getBasicBlock()->getOffset() : 666) << "." << node->getIndex() << " : " << label << "\" color=\"" << color << "\"];\n"; From 119c38ff6239750d79a4a64aee9c2191bf1da7a6 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 7 May 2016 12:16:55 +0600 Subject: [PATCH 011/105] Fixes tau optimizer by handling nodes by pairs Previous version of optimizer merged taus incorrectly. Method which produced error was Interval>>do: Producer1 \ { Aggregator <- Consumer } x 3 / Producer2 \ { Aggregator <- Consumer } x 3 Producer3 / Correct solution should be Producer1 \ / Consumer1 Aggregator - Consumer2 / \ Consumer3 Producer2 \ / Consumer4 Aggregator - Consumer5 Producer3 / \ Consumer6 but due to incorrect handling of pending nodes lists algorithm yielded the following result: Producer1 \ Producer2 - Aggregator <- Consumer x 6 Producer3 / So there was a single aggregator node that was referred by all six consumers. --- src/ControlGraph.cpp | 52 ++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 038df4b..bea1180 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -753,11 +753,16 @@ class TauLinker : public NodeVisitor { } private: - typedef std::set TTauSet; - typedef std::map TRedundantTauMap; + typedef std::pair TTauPair; + typedef std::set TTauPairSet; + + typedef std::map TRedundantTauMap; TRedundantTauMap m_redundantTaus; + + typedef std::set TTauSet; TTauSet m_processedTaus; + private: void optimizeTau() { detectRedundantTau(); @@ -769,37 +774,21 @@ class TauLinker : public NodeVisitor { for (; iProvider != m_redundantTaus.end(); ++iProvider) { printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); - TTauSet& pendingTaus = iProvider->second; - TTauSet::iterator iPendingTau = pendingTaus.begin(); + TTauPairSet& pendingTaus = iProvider->second; + TTauPairSet::iterator iPendingTau = pendingTaus.begin(); - // Skipping nodes that were processed earlier - while (iPendingTau != pendingTaus.end()) { - if (m_processedTaus.find(*iPendingTau) != m_processedTaus.end()) { - printf("Redundant tau %.2u was already processed earlier\n", - (*iPendingTau)->getIndex()); + iPendingTau = pendingTaus.begin(); + for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { + TauNode* const remainingTau = iPendingTau->first; + TauNode* const redundantTau = iPendingTau->second; - TTauSet::iterator iCurrent = iPendingTau++; - pendingTaus.erase(*iCurrent); - } else { - ++iPendingTau; + if (m_processedTaus.find(remainingTau) != m_processedTaus.end()) { + printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); + continue; } - } - if (pendingTaus.size() < 2) { - printf("Nothing to be done for provider tau %.2u\n", (*iProvider).first->getIndex()); - continue; - } - - iPendingTau = pendingTaus.begin(); - TauNode* const remainingTau = *iPendingTau++; - - for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { - TauNode* const redundantTau = *iPendingTau; const TNodeSet& consumers = redundantTau->getConsumers(); -// printf("Remapping consumers of redundant tau %.2u to remaining tau %.2u\n", -// (*iRedundantTau)->getIndex(), remainingTau->getIndex()); - // Remap all consumers to the remainingTau TNodeSet::iterator iConsumer = consumers.begin(); for ( ; iConsumer != consumers.end(); ++iConsumer) { @@ -842,6 +831,8 @@ class TauLinker : public NodeVisitor { assert(processedTau->consumers.empty()); getGraph().eraseNode(processedTau); } + + m_processedTaus.clear(); } void detectRedundantTau() { @@ -860,8 +851,6 @@ class TauLinker : public NodeVisitor { if (! tau1) continue; -// printf("Tau1 is %.2u\n", tau1->getIndex()); - TNodeSet::iterator iConsumer2 = iConsumer1; ++iConsumer2; @@ -870,14 +859,11 @@ class TauLinker : public NodeVisitor { if (!tau2) continue; -// printf("Tau2 is %.2u\n", tau2->getIndex()); - if (tau1->getIncomingSet() == tau2->getIncomingSet()) { printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); - m_redundantTaus[*iProvider].insert(tau1); - m_redundantTaus[*iProvider].insert(tau2); + m_redundantTaus[*iProvider].insert(std::make_pair(tau1, tau2)); } } } From 6c9105ba93e7e898fa1aa215ac3d32c985652193 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 9 May 2016 20:05:55 +0600 Subject: [PATCH 012/105] Adds accumulated path to GraphWalker, BackEdgeDetector refactoring --- include/analysis.h | 84 ++++++++++++++++++++++++++---------------- src/ControlGraph.cpp | 2 +- src/MethodCompiler.cpp | 9 +++-- 3 files changed, 59 insertions(+), 36 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 40d3b26..3a89b82 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -580,7 +580,15 @@ class GraphWalker { vrStopWalk }; - virtual TVisitResult visitNode(ControlNode* node) = 0; + struct TPathNode { + const ControlNode* const node; + const TPathNode* const prev; + + TPathNode(const ControlNode* node = 0, const TPathNode* prev = 0) + : node(node), prev(prev) {} + }; + + virtual TVisitResult visitNode(ControlNode& node, const TPathNode* path) = 0; virtual void nodesVisited() { } enum TWalkDirection { @@ -592,7 +600,12 @@ class GraphWalker { assert(startNode); m_direction = direction; - walkIn(startNode); + TPathNode path(startNode); + + if (visitNode(*startNode, &path) != vrKeepWalking) + return; + + walkIn(startNode, &path); nodesVisited(); } @@ -614,7 +627,7 @@ class GraphWalker { } private: - bool walkIn(ControlNode* currentNode) { + bool walkIn(ControlNode* currentNode, const TPathNode* path) { m_colorMap[currentNode] = ncGrey; const TNodeSet& nodes = (m_direction == wdForward) ? @@ -626,9 +639,11 @@ class GraphWalker { if (getNodeColor(node) != ncWhite) continue; - switch (const TVisitResult result = visitNode(node)) { + const TPathNode newPath(node, path); + + switch (const TVisitResult result = visitNode(*node, &newPath)) { case vrKeepWalking: - if (!walkIn(node)) + if (!walkIn(node, &newPath)) return false; break; @@ -665,11 +680,11 @@ class PathVerifier : public GraphWalker { } private: - virtual TVisitResult visitNode(ControlNode* node) { + virtual TVisitResult visitNode(ControlNode& node, const TPathNode*) { // Checking if there is a path between // start node and any of the destination nodes. - if (m_destinationNodes.find(node) != m_destinationNodes.end()) { + if (m_destinationNodes.find(&node) != m_destinationNodes.end()) { m_verified = true; return vrStopWalk; } @@ -682,13 +697,13 @@ class PathVerifier : public GraphWalker { bool m_verified; }; -class BackEdgeDetector { +class BackEdgeDetector : public GraphWalker { public: struct TEdge { - InstructionNode* from; - InstructionNode* to; + const InstructionNode* from; + const InstructionNode* to; - TEdge(InstructionNode* from, InstructionNode* to) + TEdge(const InstructionNode* from, const InstructionNode* to) : from(from), to(to) { assert(from); @@ -696,9 +711,22 @@ class BackEdgeDetector { } }; - typedef std::list TEdgeList; + class EdgeCompare { + public: + bool operator() (const TEdge& a, const TEdge& b) const { + if (a.from < b.from) + return true; + + if (a.from > b.from) + return false; + + return a.to < b.to; + } + }; - const TEdgeList& getBackEdges() const { return m_backEdges; } + typedef std::set TEdgeSet; + + const TEdgeSet& getBackEdges() const { return m_backEdges; } void run(ControlGraph& graph) { m_backEdges.clear(); @@ -706,32 +734,24 @@ class BackEdgeDetector { if (graph.nodes_begin() == graph.nodes_end()) return; - Walker(*this).run(*graph.nodes_begin(), GraphWalker::wdForward); + GraphWalker::run(*graph.nodes_begin(), GraphWalker::wdForward); } -private: - class Walker : public GraphWalker { - public: - Walker(BackEdgeDetector& detector) : detector(detector) {} - - virtual TVisitResult visitNode(ControlNode* node) { - if (BranchNode* const branch = node->cast()) { - InstructionNode* const target = branch->getTargetNode()->cast(); - assert(target); - - if (getNodeColor(target) == ncGrey) - detector.m_backEdges.push_back(TEdge(branch, target)); - } +protected: + virtual TVisitResult visitNode(ControlNode& node, const TPathNode*) { + if (BranchNode* const branch = node.cast()) { + InstructionNode* const target = branch->getTargetNode()->cast(); + assert(target); - return vrKeepWalking; + if (getNodeColor(target) == ncGrey) + m_backEdges.insert(TEdge(branch, target)); } - private: - BackEdgeDetector& detector; - }; + return vrKeepWalking; + } private: - TEdgeList m_backEdges; + TEdgeSet m_backEdges; }; } // namespace st diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index bea1180..2ac1b61 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -1012,7 +1012,7 @@ void ControlGraph::buildGraph() BackEdgeDetector detector; detector.run(*this); - BackEdgeDetector::TEdgeList::const_iterator iEdge = detector.getBackEdges().begin(); + BackEdgeDetector::TEdgeSet::const_iterator iEdge = detector.getBackEdges().begin(); for (; iEdge != detector.getBackEdges().end(); ++iEdge) { const BackEdgeDetector::TEdge& edge = *iEdge; diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 18806ce..c6ed339 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -176,7 +176,10 @@ class Walker : public st::GraphWalker { private: Detector& m_detector; - virtual TVisitResult visitNode(st::ControlNode* node) { return m_detector.checkNode(node); } + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + return m_detector.checkNode(&node); + } }; bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) @@ -185,8 +188,8 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) public: GCDetector() : m_detected(false) {} - virtual TVisitResult visitNode(st::ControlNode* node) { - if (st::InstructionNode* const candidate = node->cast()) { + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + if (st::InstructionNode* const candidate = node.cast()) { if (candidate->getInstruction().mayCauseGC()) { m_detected = true; return st::GraphWalker::vrStopWalk; From 0b6787354122cd69cddcb03047118c9b66081a70 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 9 May 2016 20:10:22 +0600 Subject: [PATCH 013/105] draft: refactors TauLinker to track back edges --- src/ControlGraph.cpp | 120 +++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 2ac1b61..f12c2c3 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -691,11 +691,16 @@ class GraphOptimizer : public PlainNodeVisitor { TNodeSet m_nodesToRemove; }; -class TauLinker : public NodeVisitor { +class TauLinker : private BackEdgeDetector { public: - TauLinker(ControlGraph* graph) : NodeVisitor(graph) {} + TauLinker(ControlGraph& graph) : m_graph(graph) {} + + void run() { BackEdgeDetector::run(m_graph); } private: + ControlGraph& m_graph; + ControlGraph& getGraph() { return m_graph; } + typedef std::set TInstructionSet; TInstructionSet m_pendingNodes; @@ -703,7 +708,9 @@ class TauLinker : public NodeVisitor { TTauList m_providers; private: - virtual bool visitNode(st::ControlNode& node) { + virtual st::GraphWalker::TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + st::BackEdgeDetector::visitNode(node, path); + if (InstructionNode* const instruction = node.cast()) { switch (instruction->getInstruction().getOpcode()) { case opcode::pushTemporary: @@ -740,10 +747,17 @@ class TauLinker : public NodeVisitor { } } - return true; + return st::GraphWalker::vrKeepWalking; } - virtual void domainsVisited() { + virtual void nodesVisited() { + // Detected back edges + for (TEdgeSet::const_iterator iEdge = getBackEdges().begin(); iEdge != getBackEdges().end(); ++iEdge) { + std::printf("Back edge: Node %.2u --> Node %.2u\n", + (*iEdge).from->getIndex(), + (*iEdge).to->getIndex()); + } + // When all nodes visited, process the pending list TInstructionSet::iterator iNode = m_pendingNodes.begin(); for (; iNode != m_pendingNodes.end(); ++iNode) @@ -759,7 +773,7 @@ class TauLinker : public NodeVisitor { typedef std::map TRedundantTauMap; TRedundantTauMap m_redundantTaus; - typedef std::set TTauSet; + typedef std::set TTauSet; TTauSet m_processedTaus; @@ -886,33 +900,27 @@ class TauLinker : public NodeVisitor { void processPushTemporary(InstructionNode& instruction) { // Searching for all AssignTemporary's that provide a value for current node - AssignLocator locator(instruction.getInstruction().getArgument()); + AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges()); locator.run(&instruction, GraphWalker::wdBackward); TInstructionSet::const_iterator iNode = locator.assignSites.begin(); for (; iNode != locator.assignSites.end(); ++iNode) { -// std::printf("Node %.2u is affected by node %.2u\n", -// instruction.getIndex(), (*iNode)->getIndex()); - InstructionNode* const assignTemporary = (*iNode)->cast(); assert(assignTemporary); - InstructionNode* const argument = assignTemporary->getArgument()->cast(); - if (!argument) - continue; - - TauNode* const inheritedType = assignTemporary->getTauNode(); //argument->getTauNode(); + TauNode* const assignType = assignTemporary->getTauNode(); assert(inheritedType); - // FIXME Inherit type from argument - if (! instruction.getTauNode()) { - inheritedType->addConsumer(&instruction); - instruction.setTauNode(inheritedType); + assignType->addConsumer(&instruction); + instruction.setTauNode(assignType); - std::printf("Inherit type: Tau %.2u <-- %.2u\n", - inheritedType->getIndex(), - instruction.getIndex()); + std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", + assignType->getIndex(), + instruction.getIndex(), + assignTemporary->getIndex(), + "?" + ); } else { TauNode* const current = instruction.getTauNode(); @@ -923,39 +931,63 @@ class TauLinker : public NodeVisitor { TauNode* const aggregator = getGraph().newNode(); aggregator->setKind(TauNode::tkAggregator); aggregator->addIncoming(current); - aggregator->addIncoming(inheritedType); + aggregator->addIncoming(assignType); aggregator->addConsumer(&instruction); instruction.setTauNode(aggregator); - std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u\n", + std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", instruction.getIndex(), current->getIndex(), - aggregator->getIndex()); + aggregator->getIndex(), + assignTemporary->getIndex(), + "?" + ); } else { - current->addIncoming(inheritedType); + current->addIncoming(assignType); - std::printf("Attached to existing tau: Node %.2u --> Tau %.2u\n", + std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", instruction.getIndex(), - current->getIndex()); + current->getIndex(), + assignTemporary->getIndex(), + "?" + ); } - -// std::printf("Inherit type: Tau %.2u <-- %.2u\n", -// inheritedType->getIndex(), -// instruction.getIndex()); } } } class AssignLocator : public GraphWalker { public: - AssignLocator(TSmalltalkInstruction::TArgument argument) : argument(argument) {} + AssignLocator(TSmalltalkInstruction::TArgument argument, const TEdgeSet& backEdges) + : argument(argument), backEdges(backEdges) {} - virtual TVisitResult visitNode(st::ControlNode* node) { - if (InstructionNode* const instruction = node->cast()) { + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + if (InstructionNode* const instruction = node.cast()) { if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { if (instruction->getInstruction().getArgument() == argument) { + bool hasBackEdge = false; + + // Searching for back edges in the located path + for (const TPathNode* p = path; p->prev; p = p->prev) { + const TEdgeSet::const_iterator iEdge = backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != backEdges.end()) { + hasBackEdge = true; + break; + } + } + + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + hasBackEdge ? "yes" : "no"); + assignSites.insert(instruction); return vrSkipPath; } @@ -966,6 +998,8 @@ class TauLinker : public NodeVisitor { } const TSmalltalkInstruction::TArgument argument; + const TEdgeSet& backEdges; + TInstructionSet assignSites; }; }; @@ -1004,21 +1038,7 @@ void ControlGraph::buildGraph() // Linking PushTemporary and AssignTemporary pairs { - TauLinker linker(this); + TauLinker linker(*this); linker.run(); } - - { - BackEdgeDetector detector; - detector.run(*this); - - BackEdgeDetector::TEdgeSet::const_iterator iEdge = detector.getBackEdges().begin(); - for (; iEdge != detector.getBackEdges().end(); ++iEdge) { - const BackEdgeDetector::TEdge& edge = *iEdge; - - std::printf("Back edge %u.%.2u -> %u.%.2u\n", - edge.from->getDomain()->getBasicBlock()->getOffset(), edge.from->getIndex(), - edge.to->getDomain()->getBasicBlock()->getOffset(), edge.to->getIndex()); - } - } } From 2fcf67e1d86eee82a0f2e6494206d549d268587f Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 9 May 2016 23:13:03 +0600 Subject: [PATCH 014/105] Tau nodes now store back edge flag in incoming list --- include/analysis.h | 13 ++++--- src/ControlGraph.cpp | 62 ++++++++++++++-------------------- src/ControlGraphVisualizer.cpp | 21 ++++++------ 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 3a89b82..d845ddd 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -262,16 +262,19 @@ class PhiNode : public ControlNode { // Tau node is reserved for further use in type inference subsystem. // It will link variable type transitions across a method. class TauNode : public ControlNode { + public: TauNode(uint32_t index) : ControlNode(index), m_kind(tkUnknown) { } virtual TNodeType getNodeType() const { return ntTau; } - void addIncoming(ControlNode* node) { - m_incomingSet.insert(node); + typedef std::map TIncomingMap; + + void addIncoming(ControlNode* node, bool byBackEdge = false) { + m_incomingMap[node] = byBackEdge; node->addConsumer(this); } - const TNodeSet& getIncomingSet() const { return m_incomingSet; } + const TIncomingMap& getIncomingMap() const { return m_incomingMap; } enum TKind { tkUnknown, @@ -283,8 +286,8 @@ class TauNode : public ControlNode { TKind getKind() const { return m_kind; } private: - TNodeSet m_incomingSet; - TKind m_kind; + TIncomingMap m_incomingMap; + TKind m_kind; }; // Domain is a group of nodes within a graph diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index f12c2c3..8117434 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -717,31 +717,10 @@ class TauLinker : private BackEdgeDetector { m_pendingNodes.insert(instruction); break; -// case opcode::pushConstant: -// case opcode::pushLiteral: case opcode::assignTemporary: createType(*instruction); break; -// case opcode::pushArgument: -// case opcode::sendUnary: -// case opcode::sendBinary: -// case opcode::sendMessage: -// createType(*instruction); -// break; - - case opcode::doSpecial: - switch (instruction->getInstruction().getArgument()) { -// case special::duplicate: -// inheritType(); -// break; - - case special::sendToSuper: - createType(*instruction); - break; - } - break; - default: break; } @@ -776,6 +755,14 @@ class TauLinker : private BackEdgeDetector { typedef std::set TTauSet; TTauSet m_processedTaus; + struct TAssignSite { + InstructionNode* instruction; + bool byBackEdge; + + TAssignSite(InstructionNode* instruction, bool byBackEdge) + : instruction(instruction), byBackEdge(byBackEdge) {} + }; + typedef std::vector TAssignSiteList; private: void optimizeTau() { @@ -819,13 +806,13 @@ class TauLinker : private BackEdgeDetector { } // Remove all incomings of the redundantTau - TNodeSet::iterator iIncoming = redundantTau->getIncomingSet().begin(); - for ( ; iIncoming != redundantTau->getIncomingSet().end(); ++iIncoming) { + TauNode::TIncomingMap::const_iterator iIncoming = redundantTau->getIncomingMap().begin(); + for ( ; iIncoming != redundantTau->getIncomingMap().end(); ++iIncoming) { printf("Redundant tau %.2u is no longer consumer of %.2u\n", redundantTau->getIndex(), - (*iIncoming)->getIndex()); + iIncoming->first->getIndex()); - (*iIncoming)->removeConsumer(redundantTau); + iIncoming->first->removeConsumer(redundantTau); } // Marking tau as processed @@ -873,7 +860,7 @@ class TauLinker : private BackEdgeDetector { if (!tau2) continue; - if (tau1->getIncomingSet() == tau2->getIncomingSet()) { + if (tau1->getIncomingMap() == tau2->getIncomingMap()) { printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); @@ -903,15 +890,18 @@ class TauLinker : private BackEdgeDetector { AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges()); locator.run(&instruction, GraphWalker::wdBackward); - TInstructionSet::const_iterator iNode = locator.assignSites.begin(); - for (; iNode != locator.assignSites.end(); ++iNode) { - InstructionNode* const assignTemporary = (*iNode)->cast(); + TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); + for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { + InstructionNode* const assignTemporary = (*iAssignSite).instruction->cast(); assert(assignTemporary); TauNode* const assignType = assignTemporary->getTauNode(); assert(inheritedType); if (! instruction.getTauNode()) { + // FIXME Could it be that the only incoming is accessible by back edge? + // Possbible scenario: push default nil, assigned later, branch up + assignType->addConsumer(&instruction); instruction.setTauNode(assignType); @@ -919,7 +909,7 @@ class TauLinker : private BackEdgeDetector { assignType->getIndex(), instruction.getIndex(), assignTemporary->getIndex(), - "?" + (*iAssignSite).byBackEdge ? "below" : "above" ); } else { @@ -931,7 +921,7 @@ class TauLinker : private BackEdgeDetector { TauNode* const aggregator = getGraph().newNode(); aggregator->setKind(TauNode::tkAggregator); aggregator->addIncoming(current); - aggregator->addIncoming(assignType); + aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); aggregator->addConsumer(&instruction); instruction.setTauNode(aggregator); @@ -941,17 +931,17 @@ class TauLinker : private BackEdgeDetector { current->getIndex(), aggregator->getIndex(), assignTemporary->getIndex(), - "?" + (*iAssignSite).byBackEdge ? "below" : "above" ); } else { - current->addIncoming(assignType); + current->addIncoming(assignType, (*iAssignSite).byBackEdge); std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", instruction.getIndex(), current->getIndex(), assignTemporary->getIndex(), - "?" + (*iAssignSite).byBackEdge ? "below" : "above" ); } } @@ -988,7 +978,7 @@ class TauLinker : private BackEdgeDetector { instruction->getIndex(), hasBackEdge ? "yes" : "no"); - assignSites.insert(instruction); + assignSites.push_back(TAssignSite(instruction, hasBackEdge)); return vrSkipPath; } } @@ -1000,7 +990,7 @@ class TauLinker : private BackEdgeDetector { const TSmalltalkInstruction::TArgument argument; const TEdgeSet& backEdges; - TInstructionSet assignSites; + TAssignSiteList assignSites; }; }; diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 410c922..1ef05cb 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -181,22 +181,23 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { } } else if (const st::TauNode* const tau = node.cast()) { - st::TNodeSet::const_iterator iNode = tau->getIncomingSet().begin(); - for (; iNode != tau->getIncomingSet().end(); ++iNode) { -// if ((*iNode)->getNodeType() == st::ControlNode::ntInstruction) -// continue; - + for (st::TauNode::TIncomingMap::const_iterator iNode = tau->getIncomingMap().begin(); + iNode != tau->getIncomingMap().end(); + ++iNode) + { if (tau->getKind() == st::TauNode::tkProvider) { - m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" + m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" << "weight=15 dir=back labelfloat=true color=\"red\" fontcolor=\"red\" style=\"dashed\" constraint=true ];\n"; } else { - m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" - << "weight=5 dir=back labelfloat=true color=\"grey\" fontcolor=\"green\" style=\"dashed\" constraint=true ];\n"; + const bool byBackEdge = iNode->second; + m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" + << "weight=5 dir=back labelfloat=true color=\"" + << (byBackEdge ? "blue" : "grey") + << "\" fontcolor=\"green\" style=\"dotted\" constraint=true ];\n"; } } - iNode = tau->getConsumers().begin(); - for (; iNode != tau->getConsumers().end(); ++iNode) { + for (st::TNodeSet::iterator iNode = tau->getConsumers().begin(); iNode != tau->getConsumers().end(); ++iNode) { if ((*iNode)->getNodeType() == st::ControlNode::ntTau) continue; From 7e70c0d680d8979bf8d43ac99b5599a74acba9c3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 11 May 2016 00:45:26 +0600 Subject: [PATCH 015/105] Fixes graph linker in case of assign node first in the domain AssignX instructions leave their argument on the stack. That was causing problems during processing of argument requests. --- include/analysis.h | 6 +++++- src/ControlGraph.cpp | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index d845ddd..1c8ce0e 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -334,13 +334,17 @@ class ControlDomain { argument->addEdge(forNode); } else { - m_reqestedArguments.push_back((TArgumentRequest){index, forNode}); + m_reqestedArguments.push_back(TArgumentRequest(index, forNode, keep)); } } struct TArgumentRequest { std::size_t index; InstructionNode* requestingNode; + bool keep; + + TArgumentRequest(std::size_t index, InstructionNode* requestingNode, bool keep) + : index(index), requestingNode(requestingNode), keep(keep) {} }; typedef std::vector TRequestList; diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 8117434..aa0db8e 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -366,8 +366,11 @@ void GraphLinker::processBranching() void GraphLinker::processArgumentRequests() { const ControlDomain::TRequestList& requestList = m_currentDomain->getRequestedArguments(); - for (std::size_t index = 0; index < requestList.size(); index++) - processRequest(m_currentDomain, index, requestList[index]); + for (std::size_t index = 0, argIndex = 0; index < requestList.size(); index++) { + processRequest(m_currentDomain, argIndex, requestList[index]); + if (!requestList[index].keep) + argIndex++; + } } void GraphLinker::processRequest(ControlDomain* domain, std::size_t argumentIndex, const ControlDomain::TArgumentRequest& request) @@ -431,6 +434,7 @@ ControlNode* GraphLinker::optimizePhi(PhiNode* phi) // This is the real value that should be returned ControlNode* const value = *incomingValues.begin(); + assert(value); // Unlink and erase phi value->removeConsumer(phi); From 61a25436ba30329d2a84da822b99088abb1d8b5b Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 11 May 2016 23:11:52 +0600 Subject: [PATCH 016/105] Cleans up control graph visualizer --- src/ControlGraphVisualizer.cpp | 48 ++++++---------------------------- 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 1ef05cb..e0f561b 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -74,18 +74,11 @@ bool ControlGraphVisualizer::visitDomain(st::ControlDomain& /*domain*/) { } std::string edgeStyle(st::ControlNode* from, st::ControlNode* to) { -// const st::InstructionNode* const fromInstruction = from->cast(); const st::InstructionNode* const toInstruction = to->cast(); if (from->getNodeType() == st::ControlNode::ntPhi && to->getNodeType() == st::ControlNode::ntPhi) return "[style=invis color=red constraint=false]"; -// if (from->getNodeType() == st::ControlNode::ntTau && to->getNodeType() == st::ControlNode::ntTau) -// return "[style=invis color=red constraint=false]"; - -// if (fromInstruction && fromInstruction->getInstruction().isBranch()) -// return "[color=\"grey\" style=\"dashed\"]"; - if (toInstruction && toInstruction->getArgumentsCount() == 0) return "[weight=100 color=\"black\" style=\"dashed\" ]"; @@ -99,7 +92,7 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { // Processing incoming edges st::TNodeSet::iterator iEdge = inEdges.begin(); for (; iEdge != inEdges.end(); ++iEdge) { - if (isNodeProcessed(*iEdge)) + if (isNodeProcessed(*iEdge) || (*iEdge)->getNodeType() == st::ControlNode::ntPhi) continue; if (const st::InstructionNode* const instruction = (*iEdge)->cast()) { @@ -123,47 +116,22 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { m_stream << "label=" << index; m_stream << "dir=back weight=8 labelfloat=true color=\"blue\" fontcolor=\"blue\" style=\"dashed\" constraint=true];\n"; - - - /*if (const st::InstructionNode* const instructionArg = instruction->getArgument(index)->cast()) { - if (const st::TauNode* const argumentTau = instructionArg->getTauNode()) { - m_stream << "\t\t" << argumentTau->getIndex() << " -> " << instructionArg->getIndex() << " [" - << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" " - << "constraint=false ];\n"; - } - }*/ - } if (const st::BranchNode* const branch = node.cast()) { - m_stream << "\t\t" << node.getIndex() << " -> " << branch->getTargetNode()->getIndex() << " ["; - m_stream << "weight=20 label=target labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + m_stream + << "\t\t" << node.getIndex() << " -> " << branch->getTargetNode()->getIndex() << " [" + << (branch->getSkipNode() ? " label=target " : "") + << "weight=20 labelfloat=true color=\"grey\" fontcolor=\"grey\" style=\"dashed\"];\n"; if (branch->getSkipNode()) { - m_stream << "\t\t" << node.getIndex() << " -> " << branch->getSkipNode()->getIndex() << " ["; - m_stream << "weight=20 label=skip labelfloat=true color=\"grey\" style=\"dashed\"];\n"; + m_stream + << "\t\t" << node.getIndex() << " -> " << branch->getSkipNode()->getIndex() << " [" + << "weight=20 label=skip labelfloat=true color=\"grey\" fontcolor=\"grey\" style=\"dashed\"];\n"; } outEdgesProcessed = true; } - - /*if (const st::TauNode* const tau = instruction->getTauNode()) { - const char* constraint = tau->getIncomingSet().size() == 1 ? "true" : "false"; - - m_stream << "\t\t" << instruction->getIndex() << " -> " << tau->getIndex() << " [" - << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" " - << "constraint=" << constraint << " ];\n"; - }*/ - - /*st::TNodeSet::const_iterator iNode = instruction->getConsumers().begin(); - for (; iNode != instruction->getConsumers().end(); ++iNode) { - if ((*iNode)->getNodeType() != st::ControlNode::ntTau) - continue; - - m_stream << "\t\t" << (*iNode)->getIndex() << " -> " << tau->getIndex() << " [" - << "labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" constraint=false ];\n"; - }*/ - } else if (const st::PhiNode* const phi = node.cast()) { m_stream << "\t\t" << phi->getIndex() << " -> " << phi->getDomain()->getEntryPoint()->getIndex() << " [" From ec1644cf893da89f43d629fa4dd99c6d2de2d2e4 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 15 May 2016 19:13:51 +0600 Subject: [PATCH 017/105] Minor fixes in graph api --- include/analysis.h | 3 ++- src/ControlGraph.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 1c8ce0e..875c0ed 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -248,7 +248,7 @@ class PhiNode : public ControlNode { } const TIncomingList& getIncomingList() const { return m_incomingList; } - TNodeSet getRealValues(); + TNodeSet getRealValues() const; llvm::PHINode* getPhiValue() const { return m_phiValue; } void setPhiValue(llvm::PHINode* value) { m_phiValue = value; } @@ -388,6 +388,7 @@ class ControlGraph { typedef TNodeList::iterator nodes_iterator; nodes_iterator nodes_begin() { return m_nodes.begin(); } nodes_iterator nodes_end() { return m_nodes.end(); } + bool isEmpty() const { return m_nodes.begin() == m_nodes.end(); } ControlNode* newNode(ControlNode::TNodeType type) { assert(type == ControlNode::ntInstruction diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index aa0db8e..df23e01 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -63,7 +63,7 @@ template<> BranchNode* ControlGraph::newNode() { return static_cast(node); } -TNodeSet PhiNode::getRealValues() { +TNodeSet PhiNode::getRealValues() const { TNodeSet values; for (std::size_t i = 0; i < m_incomingList.size(); i++) { From 5b3a6377c11e60137aabb632e4b252c7efeb06c1 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 15 May 2016 19:28:32 +0600 Subject: [PATCH 018/105] Adds basic logic of type analyzer and inference API Issue: #17 --- CMakeLists.txt | 1 + include/inference.h | 176 ++++++++++++++++++++++++++++++++++++++++ src/TypeAnalyzer.cpp | 187 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+) create mode 100644 include/inference.h create mode 100644 src/TypeAnalyzer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 98dfac4..0d4679d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ add_library(stapi src/ControlGraph.cpp src/ControlGraphVisualizer.cpp + src/TypeAnalyzer.cpp ) set(MM_CPP_FILES diff --git a/include/inference.h b/include/inference.h new file mode 100644 index 0000000..3b9fdbf --- /dev/null +++ b/include/inference.h @@ -0,0 +1,176 @@ +#ifndef LLST_INFERENCE_H_INCLUDED +#define LLST_INFERENCE_H_INCLUDED + +#include +#include + +namespace type { + +using namespace st; + +class Type { +public: + enum TKind { + tkUndefined = 0, + tkLiteral, + tkMonotype, + tkComposite, + tkArray, + tkPolytype + }; + + // Return a string representation of a type: + // Kind Representation Example + // tkUndefined ? ? + // tkPolytype * * + // tkLiteral literal value 42 + // tkMonotype (class name) (SmallInt) + // tkComposite (class name, ...) (SmallInt, *) + // tkArray class name [...] Array[String, *, (*, *), (True, False)] + std::string toString() const; + + Type(TKind kind = tkUndefined) : m_kind(kind), m_value(0) {} + Type(TObject* literal) { set(literal); } + Type(TClass* klass) { set(klass); } + + void setKind(TKind kind) { m_kind = kind; } + TKind getKind() const { return m_kind; } + TObject* getValue() const { return m_value; } + + void reset() { + m_kind = tkUndefined; + m_value = 0; + m_subTypes.clear(); + } + + void set(TObject* literal, TKind kind = tkLiteral) { + m_kind = kind; + m_value = literal; + } + + void set(TClass* klass, TKind kind = tkMonotype) { + m_kind = kind; + m_value = klass; + } + + typedef std::vector TSubTypes; + const TSubTypes& getSubTypes() const { return m_subTypes; } + const Type& operator[] (std::size_t index) const { return m_subTypes[index]; } + + void addSubType(const Type& type) { m_subTypes.push_back(type); } + +private: + TKind m_kind; + TObject* m_value; + TSubTypes m_subTypes; +}; + +typedef std::vector TTypeList; + +class CallContext { +public: + CallContext(std::size_t index, const Type& arguments, std::size_t nodeCount) + : m_index(index), m_arguments(arguments) + { + m_instructions.resize(nodeCount); + } + + std::size_t getIndex() const { return m_index; } + + const Type& getArgument(std::size_t index) const { + static const Type polytype(Type::tkPolytype); + + if (m_arguments.getKind() != Type::tkPolytype) + return m_arguments[index]; + else + return polytype; + } + const Type& getArguments() const { return m_arguments; } + + Type& getReturnType() { return m_returnType; } + + Type& getInstructionType(std::size_t index) { return m_instructions[index]; } + Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } + + +private: + const std::size_t m_index; + const Type m_arguments; + TTypeList m_instructions; + Type m_returnType; +}; + +class TypeSystem { +public: + CallContext* newCallContext(TMethod* method, const Type& arguments = Type(Type::tkPolytype)); + + CallContext* getCallContext(std::size_t index); +}; + +class TypeAnalyzer { +public: + TypeAnalyzer(ControlGraph& graph, CallContext& context) + : m_graph(graph), m_context(context) {} + + void run() { + if (m_graph.isEmpty()) + return; + + Walker walker(*this); + walker.run(*m_graph.nodes_begin(), Walker::wdForward); + } + +private: + ControlGraph& m_graph; + CallContext& m_context; + +private: + void processInstruction(const InstructionNode& instruction); + void processPhi(const PhiNode& phi); + void processTau(const TauNode& tau); + void walkComplete(); + + void doMarkArguments(const InstructionNode& instruction); + void doPushConstant(const InstructionNode& instruction); + void doPushLiteral(const InstructionNode& instruction); + void doSendUnary(const InstructionNode& instruction); + void doSendBinary(const InstructionNode& instruction); + +private: + + class Walker : public GraphWalker { + public: + Walker(TypeAnalyzer& analyzer) : analyzer(analyzer) {} + + private: + TVisitResult visitNode(ControlNode& node, const TPathNode*) { + switch (node.getNodeType()) { + case ControlNode::ntInstruction: + analyzer.processInstruction(static_cast(node)); + break; + + case ControlNode::ntPhi: + analyzer.processPhi(static_cast(node)); + break; + + case ControlNode::ntTau: + analyzer.processTau(static_cast(node)); + break; + } + + return vrKeepWalking; + } + + void nodesVisited() { + analyzer.walkComplete(); + } + + private: + TypeAnalyzer& analyzer; + }; + +}; + +} // namespace type + +#endif diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp new file mode 100644 index 0000000..81e6286 --- /dev/null +++ b/src/TypeAnalyzer.cpp @@ -0,0 +1,187 @@ +#include + +using namespace type; + +void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + + switch (instruction.getInstruction().getOpcode()) { + case opcode::pushArgument: + m_context[instruction] = m_context.getArgument(argument); + break; + + case opcode::pushConstant: doPushConstant(instruction); break; + case opcode::pushLiteral: doPushLiteral(instruction); break; + case opcode::markArguments: doMarkArguments(instruction); break; + + case opcode::sendBinary: doSendBinary(instruction); break; + + case opcode::sendMessage: + // For now, treat method call as * + m_context[instruction] = Type(Type::tkPolytype); + break; + + default: + break; + } +} + +void TypeAnalyzer::doPushConstant(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + Type& type = m_context[instruction]; + + switch (argument) { + case 0: case 1: case 2: case 3: case 4: + case 5: case 6: case 7: case 8: case 9: + type.set(TInteger(argument)); + break; + + case pushConstants::nil: type.set(globals.nilObject); break; + case pushConstants::trueObject: type.set(globals.trueObject); break; + case pushConstants::falseObject: type.set(globals.falseObject); break; + + default: + std::fprintf(stderr, "VM: unknown push constant %d\n", argument); + type.reset(); + } +} + +void TypeAnalyzer::doPushLiteral(const InstructionNode& instruction) { + TMethod* const method = m_graph.getParsedMethod()->getOrigin(); + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + TObject* const literal = method->literals->getField(argument); + + m_context[instruction] = Type(literal); +} + +void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { + const Type& argType = m_context[*instruction.getArgument()]; + const unaryBuiltIns::Opcode opcode = static_cast(instruction.getInstruction().getArgument()); + + Type& result = m_context[instruction]; + switch (argType.getKind()) { + case Type::tkLiteral: + case Type::tkMonotype: + { + const bool isValueNil = + (argType.getValue() == globals.nilObject) + || (argType.getValue() == globals.nilObject->getClass()); + + if (opcode == unaryBuiltIns::isNil) + result.set(isValueNil ? globals.trueObject : globals.falseObject); + else + result.set(isValueNil ? globals.falseObject : globals.trueObject); + break; + } + + case Type::tkComposite: + case Type::tkArray: + { + // TODO Repeat the procedure over each subtype + result.setKind(Type::tkPolytype); + break; + } + + default: + // * isNil = (Boolean) + // * notNil = (Boolean) + result.set(globals.trueObject->getClass()->getClass()); + } + +} + +void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { + const Type& type1 = m_context[*instruction.getArgument(0)]; + const Type& type2 = m_context[*instruction.getArgument(1)]; + const binaryBuiltIns::Operator opcode = static_cast(instruction.getInstruction().getArgument()); + + Type& result = m_context[instruction]; + + if (isSmallInteger(type1.getValue()) && isSmallInteger(type1.getValue())) { + const int32_t rightOperand = TInteger(type2.getValue()); + const int32_t leftOperand = TInteger(type1.getValue()); + + switch (opcode) { + case binaryBuiltIns::operatorLess: + result.set((leftOperand < rightOperand) ? globals.trueObject : globals.falseObject); + break; + + case binaryBuiltIns::operatorLessOrEq: + result.set((leftOperand <= rightOperand) ? globals.trueObject : globals.falseObject); + break; + + case binaryBuiltIns::operatorPlus: + result.set(TInteger(leftOperand + rightOperand)); + break; + + default: + std::fprintf(stderr, "VM: Invalid opcode %d passed to sendBinary\n", opcode); + } + + return; + } + + // Literal int or (SmallInt) monotype + const bool isInt1 = isSmallInteger(type1.getValue()) || type1.getValue() == globals.smallIntClass; + const bool isInt2 = isSmallInteger(type2.getValue()) || type2.getValue() == globals.smallIntClass; + + if (isInt1 && isInt2) { + switch (opcode) { + case binaryBuiltIns::operatorLess: + case binaryBuiltIns::operatorLessOrEq: + // (SmallInt) <= (SmallInt) = (Boolean) + result.set(globals.trueObject->getClass()->getClass()); + break; + + case binaryBuiltIns::operatorPlus: + // (SmallInt) + (SmallInt) = (SmallInt) + result.set(globals.smallIntClass); + break; + + default: + std::fprintf(stderr, "VM: Invalid opcode %d passed to sendBinary\n", opcode); + result.reset(); // ? + } + + return; + } + + // TODO In case of complex invocation encode resursive analysis of operator as a message + + result.setKind(Type::tkPolytype); +} + +void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { + const Type& argsType = m_context[*instruction.getArgument()]; + Type& result = m_context[instruction]; + + if (argsType.getKind() == Type::tkUndefined || argsType.getKind() == Type::tkPolytype) { + result.set(globals.arrayClass); + return; + } + + for (std::size_t index = 0; index < argsType.getSubTypes().size(); index++) + result.addSubType(argsType[index]); + + result.set(globals.arrayClass, Type::tkArray); +} + +void TypeAnalyzer::processPhi(const PhiNode& phi) { + Type& result = m_context[phi]; + + const TNodeSet& incomings = phi.getRealValues(); + TNodeSet::iterator iNode = incomings.begin(); + for (; iNode != incomings.end(); ++iNode) + result.addSubType(m_context[*(*iNode)->cast()]); + + result.setKind(Type::tkComposite); +} + +void TypeAnalyzer::processTau(const TauNode& tau) { + Type& result = m_context[tau]; + result.setKind(Type::tkPolytype); +} + +void TypeAnalyzer::walkComplete() { + +} From 44a1370f99140cebc328d507daad24c38e53b696 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 15 May 2016 20:23:19 +0600 Subject: [PATCH 019/105] Adds Type::toString() Issue: #17 --- include/types.h | 1 + src/TypeAnalyzer.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/include/types.h b/include/types.h index 2a8190d..eab3527 100644 --- a/include/types.h +++ b/include/types.h @@ -235,6 +235,7 @@ struct TSymbol : public TByteObject { // Strings are binary objects that hold raw character bytes. struct TString : public TByteObject { static const char* InstanceClassName() { return "String"; } + std::string toString() const { return std::string(reinterpret_cast(bytes), getSize()); } }; // Chars are intermediate representation of single printable character diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 81e6286..638c859 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -2,6 +2,51 @@ using namespace type; +std::string Type::toString() const { + std::stringstream stream; + + switch (m_kind) { + case tkUndefined: return "?"; + case tkPolytype: return "*"; + + case tkLiteral: + if (isSmallInteger(getValue())) + stream << TInteger(getValue()).getValue(); + else if (getValue() == globals.nilObject) + return "nil"; + else if (getValue() == globals.trueObject) + return "true"; + else if (getValue() == globals.falseObject) + return "false"; + else if (getValue()->getClass() == globals.stringClass) + stream << "'" << getValue()->cast()->toString() << "'"; + else if (getValue()->getClass() == globals.badMethodSymbol->getClass()) + stream << "'" << getValue()->cast()->toString() << "'"; + break; + + case tkMonotype: + stream << "(" << getValue()->cast()->name->toString() << ")"; + break; + + case tkArray: + stream << getValue()->cast()->name->toString(); + case tkComposite: { + stream << (m_kind == tkComposite ? "(" : "["); + + for (std::size_t index = 0; index < getSubTypes().size(); index++) { + if (index) + stream << ", "; + + stream << m_subTypes[index].toString(); + } + + stream << (m_kind == tkComposite ? ")" : "]"); + }; + } + + return stream.str(); +} + void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); From 3ac8c0a11c7a50c1e13bdc2d4ebd971c5ed03935 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 15 May 2016 20:46:19 +0600 Subject: [PATCH 020/105] Adds CallContext::operator[index] Issue: #17 --- include/inference.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/inference.h b/include/inference.h index 3b9fdbf..6e5c920 100644 --- a/include/inference.h +++ b/include/inference.h @@ -90,9 +90,9 @@ class CallContext { Type& getReturnType() { return m_returnType; } Type& getInstructionType(std::size_t index) { return m_instructions[index]; } + Type& operator[] (std::size_t index) { return m_instructions[index]; } Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } - private: const std::size_t m_index; const Type m_arguments; From 97ea0cba69c4b87447b5a1480c2727956175d9ff Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 16 May 2016 11:17:14 +0600 Subject: [PATCH 021/105] Adds const cast for BranchNode Issue: #17 --- include/analysis.h | 2 ++ src/ControlGraph.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/analysis.h b/include/analysis.h index 875c0ed..5448652 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -105,6 +105,7 @@ class ControlNode { // Dynamically cast node to a specified type. // If type does not match null is returned. template T* cast(); + template const T* cast() const; uint32_t getIndex() const { return m_index; } @@ -484,6 +485,7 @@ template<> PushBlockNode* ControlNode::cast(); template<> PushBlockNode* ControlGraph::newNode(); template<> BranchNode* ControlNode::cast(); +template<> const BranchNode* ControlNode::cast() const; template<> BranchNode* ControlGraph::newNode(); class DomainVisitor { diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index df23e01..13208b5 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -57,6 +57,10 @@ template<> BranchNode* ControlNode::cast() { return 0; } +template<> const BranchNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + template<> BranchNode* ControlGraph::newNode() { BranchNode* const node = new BranchNode(m_lastNodeIndex++); m_nodes.push_back(node); From d6db564ec0eccdea769f59a4a0a2f16ec600990c Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 16 May 2016 11:19:19 +0600 Subject: [PATCH 022/105] Fixes analyzer and context, adds handling of conditional branches Issue: #17 --- include/inference.h | 32 +++++++++---------- src/TypeAnalyzer.cpp | 74 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/include/inference.h b/include/inference.h index 6e5c920..1cf8964 100644 --- a/include/inference.h +++ b/include/inference.h @@ -30,8 +30,8 @@ class Type { std::string toString() const; Type(TKind kind = tkUndefined) : m_kind(kind), m_value(0) {} - Type(TObject* literal) { set(literal); } - Type(TClass* klass) { set(klass); } + Type(TObject* literal, TKind kind = tkLiteral) { set(literal, kind); } + Type(TClass* klass, TKind kind = tkMonotype) { set(klass, kind); } void setKind(TKind kind) { m_kind = kind; } TKind getKind() const { return m_kind; } @@ -65,15 +65,13 @@ class Type { TSubTypes m_subTypes; }; -typedef std::vector TTypeList; +typedef std::size_t TNodeIndex; +typedef std::map TTypeList; class CallContext { public: - CallContext(std::size_t index, const Type& arguments, std::size_t nodeCount) - : m_index(index), m_arguments(arguments) - { - m_instructions.resize(nodeCount); - } + CallContext(std::size_t index, const Type& arguments) + : m_index(index), m_arguments(arguments) {} std::size_t getIndex() const { return m_index; } @@ -86,11 +84,12 @@ class CallContext { return polytype; } const Type& getArguments() const { return m_arguments; } + const TTypeList& getTypeList() const { return m_instructions; } Type& getReturnType() { return m_returnType; } - Type& getInstructionType(std::size_t index) { return m_instructions[index]; } - Type& operator[] (std::size_t index) { return m_instructions[index]; } + Type& getInstructionType(TNodeIndex index) { return m_instructions[index]; } + Type& operator[] (TNodeIndex index) { return m_instructions[index]; } Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } private: @@ -110,20 +109,15 @@ class TypeSystem { class TypeAnalyzer { public: TypeAnalyzer(ControlGraph& graph, CallContext& context) - : m_graph(graph), m_context(context) {} + : m_graph(graph), m_context(context), m_walker(*this) {} void run() { if (m_graph.isEmpty()) return; - Walker walker(*this); - walker.run(*m_graph.nodes_begin(), Walker::wdForward); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); } -private: - ControlGraph& m_graph; - CallContext& m_context; - private: void processInstruction(const InstructionNode& instruction); void processPhi(const PhiNode& phi); @@ -169,6 +163,10 @@ class TypeAnalyzer { TypeAnalyzer& analyzer; }; +private: + ControlGraph& m_graph; + CallContext& m_context; + Walker m_walker; }; } // namespace type diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 638c859..6bcd31c 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -21,7 +21,11 @@ std::string Type::toString() const { else if (getValue()->getClass() == globals.stringClass) stream << "'" << getValue()->cast()->toString() << "'"; else if (getValue()->getClass() == globals.badMethodSymbol->getClass()) - stream << "'" << getValue()->cast()->toString() << "'"; + stream << "#" << getValue()->cast()->toString(); + else if (getValue()->getClass()->name->toString().find("Meta", 0, 4) != std::string::npos) + stream << getValue()->cast()->name->toString(); + else + stream << "~" << getValue()->getClass()->name->toString(); break; case tkMonotype: @@ -48,6 +52,8 @@ std::string Type::toString() const { } void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { + std::printf("processing %.2u\n", instruction.getIndex()); + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); switch (instruction.getInstruction().getOpcode()) { @@ -59,13 +65,61 @@ void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { case opcode::pushLiteral: doPushLiteral(instruction); break; case opcode::markArguments: doMarkArguments(instruction); break; + case opcode::sendUnary: doSendUnary(instruction); break; case opcode::sendBinary: doSendBinary(instruction); break; + case opcode::assignTemporary: + m_context[instruction.getTauNode()->getIndex()] = m_context[*instruction.getArgument()]; + break; + case opcode::sendMessage: // For now, treat method call as * m_context[instruction] = Type(Type::tkPolytype); break; + case opcode::doPrimitive: + m_context.getReturnType().addSubType(Type(Type::tkPolytype)); + break; + + case opcode::doSpecial: { + switch (argument) { + case special::branchIfFalse: + case special::branchIfTrue: { + const bool branchIfTrue = (argument == special::branchIfTrue); + + const Type& argType = m_context[*instruction.getArgument()]; + const BranchNode* const branch = instruction.cast(); + + if (argType.getValue() == globals.trueObject || argType.getValue() == globals.trueObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getSkipNode() : branch->getTargetNode()); + else if (argType.getValue() == globals.falseObject || argType.getValue() == globals.falseObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getTargetNode() : branch->getSkipNode()); + + break; + } + + case special::stackReturn: + m_context.getReturnType().addSubType(m_context[*instruction.getArgument()]); + break; + + case special::selfReturn: + m_context.getReturnType().addSubType(m_context.getArgument(0)); + break; + + case special::sendToSuper: + // For now, treat method call as * + m_context[instruction] = Type(Type::tkPolytype); + break; + + case special::duplicate: + m_context[instruction] = m_context[*instruction.getArgument()]; + break; + } + + break; + } + + default: break; } @@ -130,7 +184,7 @@ void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { default: // * isNil = (Boolean) // * notNil = (Boolean) - result.set(globals.trueObject->getClass()->getClass()); + result.set(globals.trueObject->getClass()->parentClass); } } @@ -142,7 +196,7 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { Type& result = m_context[instruction]; - if (isSmallInteger(type1.getValue()) && isSmallInteger(type1.getValue())) { + if (isSmallInteger(type1.getValue()) && isSmallInteger(type2.getValue())) { const int32_t rightOperand = TInteger(type2.getValue()); const int32_t leftOperand = TInteger(type1.getValue()); @@ -175,7 +229,7 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { case binaryBuiltIns::operatorLess: case binaryBuiltIns::operatorLessOrEq: // (SmallInt) <= (SmallInt) = (Boolean) - result.set(globals.trueObject->getClass()->getClass()); + result.set(globals.trueObject->getClass()->parentClass); break; case binaryBuiltIns::operatorPlus: @@ -197,17 +251,13 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { } void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { - const Type& argsType = m_context[*instruction.getArgument()]; Type& result = m_context[instruction]; - if (argsType.getKind() == Type::tkUndefined || argsType.getKind() == Type::tkPolytype) { - result.set(globals.arrayClass); - return; + for (std::size_t index = 0; index < instruction.getArgumentsCount(); index++) { + const Type& argsType = m_context[*instruction.getArgument(index)]; + result.addSubType(argsType); } - for (std::size_t index = 0; index < argsType.getSubTypes().size(); index++) - result.addSubType(argsType[index]); - result.set(globals.arrayClass, Type::tkArray); } @@ -228,5 +278,5 @@ void TypeAnalyzer::processTau(const TauNode& tau) { } void TypeAnalyzer::walkComplete() { - + std::printf("walk complete\n"); } From 3a66f0c545c2a881c294256c8294c95417c754f9 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 12:28:08 +0600 Subject: [PATCH 023/105] Adds order, comparison and composition operators to the Type It allows to compare any two types, store them in STL container such as std::set, use them as a key in std::map and use composition operators during type inference procedure. Operator | is a disjunction-like operator used to get sum of several possible types within a type. For example: 2 | 2 -> 2 2 | 3 -> (2, 3) 2 | * -> (2, *) (Object) | (SmallInt) -> ((Object), (SmallInt)) This operator may be used to aggregate possible types within a linear sequence where several type outcomes are possible: x <- y isNil ifTrue: [ nil ] ifFalse: [ 42 ]. In this case x will have composite type (nil, 42). On the other hand, when dealing with loops we need some kind of a reduction operator that will act as a conjunction: 2 & 2 -> 2 2 & 3 -> (SmallInt) 2 & (SmallInt) -> (SmallInt) & * -> * (SmallInt) & (Object) -> * This operator is used during induction run of the type analyzer to prove that variable does not leave it's local type domain, i.e it's type is not reduced to a *. Issue: #17 --- include/inference.h | 159 +++++++++++++++++++++++++++++++++++++++++-- src/TypeAnalyzer.cpp | 5 +- 2 files changed, 158 insertions(+), 6 deletions(-) diff --git a/include/inference.h b/include/inference.h index 1cf8964..665aae2 100644 --- a/include/inference.h +++ b/include/inference.h @@ -4,6 +4,8 @@ #include #include +#include + namespace type { using namespace st; @@ -27,16 +29,20 @@ class Type { // tkMonotype (class name) (SmallInt) // tkComposite (class name, ...) (SmallInt, *) // tkArray class name [...] Array[String, *, (*, *), (True, False)] - std::string toString() const; + std::string toString(bool subtypesOnly = false) const; Type(TKind kind = tkUndefined) : m_kind(kind), m_value(0) {} Type(TObject* literal, TKind kind = tkLiteral) { set(literal, kind); } Type(TClass* klass, TKind kind = tkMonotype) { set(klass, kind); } + Type(const Type& copy) : m_kind(copy.m_kind), m_value(copy.m_value), m_subTypes(copy.m_subTypes) {} + void setKind(TKind kind) { m_kind = kind; } TKind getKind() const { return m_kind; } TObject* getValue() const { return m_value; } + typedef std::vector TSubTypes; + void reset() { m_kind = tkUndefined; m_value = 0; @@ -53,11 +59,156 @@ class Type { m_value = klass; } - typedef std::vector TSubTypes; const TSubTypes& getSubTypes() const { return m_subTypes; } - const Type& operator[] (std::size_t index) const { return m_subTypes[index]; } - void addSubType(const Type& type) { m_subTypes.push_back(type); } + void addSubType(const Type& type) { + if (std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) + m_subTypes.push_back(type); + } + + const Type& operator [] (std::size_t index) const { return m_subTypes[index]; } + + bool operator < (const Type& other) const { + if (m_kind != other.m_kind) + return m_kind < other.m_kind; + + if (m_value != other.m_value) + return m_value < other.m_value; + + if (m_subTypes.size() != other.m_subTypes.size()) + return m_subTypes.size() < other.m_subTypes.size(); + + for (std::size_t index = 0; index < m_subTypes.size(); index++) { + if (m_subTypes[index] < other.m_subTypes[index]) + return true; + } + + return false; + } + + bool operator == (const Type& other) const { + if (m_kind != other.m_kind) + return false; + + if (m_value != other.m_value) + return false; + + if (m_subTypes != other.m_subTypes) + return false; + + return true; + } + + Type& operator = (const Type& other) { + m_kind = other.m_kind; + m_value = other.m_value; + m_subTypes = other.m_subTypes; + + return *this; + } + + Type operator | (const Type& other) const { return Type(*this) |= other; } + Type operator & (const Type& other) const { return Type(*this) &= other; } + + Type& operator |= (const Type& other) { + if (*this == other) + return *this; + + if (m_kind == tkUndefined) + return *this = other; + + if (m_kind != tkComposite) { + m_subTypes.push_back(*this); + m_kind = tkComposite; + } + + if (other.m_kind != tkComposite) { + addSubType(other); + } else { + for (std::size_t index = 0; index < other.m_subTypes.size(); index++) + addSubType(other[index]); + } + + return *this; + } + + // ? & _ -> ? + // * & _ -> * + + // 2 & 2 -> 2 + // 2 & 3 -> (SmallInt) + // 2 & (SmallInt) -> (SmallInt) + // true & false -> (Boolean) + + // (2, 3) & (SmallInt) -> (SmallInt) + // (SmallInt) & (SmallInt) -> (SmallInt) + // (SmallInt) & (SmallInt) -> (SmallInt) + // (SmallInt) & true -> * + // (SmallInt) & (Object) -> * + + // Array[2,3] & (Array) -> (Array) + // + Type& operator &= (const Type& other) { + if (other.m_kind == tkUndefined || other.m_kind == tkPolytype) + return *this = Type((m_kind == tkUndefined) ? tkUndefined : other.m_kind); + + switch (m_kind) { + case tkUndefined: + case tkPolytype: + return *this = Type((other.m_kind == tkUndefined) ? tkUndefined : m_kind); + + case tkLiteral: + if (m_value == other.m_value) { // 2 & 3 + return *this; // 2 & 2 + } else { + // TODO true & false -> (Boolean) + TClass* const klass = isSmallInteger(m_value) ? globals.smallIntClass : m_value->getClass(); + return *this = (Type(klass) &= other); + } + + case tkMonotype: { + if (m_value == other.m_value) // (SmallInt) & (Object) + return *this; // (SmallInt) & (SmallInt) + + TObject* const otherValue = other.m_value; + TClass* const otherKlass = isSmallInteger(otherValue) ? globals.smallIntClass : otherValue->getClass(); + if (other.m_kind == tkLiteral && m_value == otherKlass) + return *this; // (SmallInt) & 42 + else + return *this = Type(tkPolytype); + } + + case tkArray: + if (other.m_kind == tkArray) { // Array[2, 3] & Array[2, 3] + if (m_value == other.m_value) { // Array[true, false] & Object[true, false] + if (*this == other) + return *this; + else + return *this = Type(m_value, tkMonotype); // Array[2, 3] & (Array) + } + } + return *this = (Type(m_value) &= other); + + case tkComposite: + if (m_subTypes.empty()) { + reset(); + return *this; + } + + Type& result = m_subTypes[0]; + for (std::size_t index = 1; index < m_subTypes.size(); index++) { + result &= m_subTypes[index]; + + if (result.getKind() == tkUndefined || result.getKind() == tkPolytype) + return *this = result; + } + + return *this = result; + } + + reset(); + return *this; + } private: TKind m_kind; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 6bcd31c..1b54798 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -2,7 +2,7 @@ using namespace type; -std::string Type::toString() const { +std::string Type::toString(bool subtypesOnly /*= false*/) const { std::stringstream stream; switch (m_kind) { @@ -33,7 +33,8 @@ std::string Type::toString() const { break; case tkArray: - stream << getValue()->cast()->name->toString(); + if (subtypesOnly) + stream << getValue()->cast()->name->toString(); case tkComposite: { stream << (m_kind == tkComposite ? "(" : "["); From 1cc3e65b9f3e27fc9d2f1857f23601a991f0fc72 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 12:30:51 +0600 Subject: [PATCH 024/105] Hides trace messages in ControlGraph under condition --- src/ControlGraph.cpp | 116 +++++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 13208b5..aa36222 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -740,9 +740,12 @@ class TauLinker : private BackEdgeDetector { virtual void nodesVisited() { // Detected back edges for (TEdgeSet::const_iterator iEdge = getBackEdges().begin(); iEdge != getBackEdges().end(); ++iEdge) { - std::printf("Back edge: Node %.2u --> Node %.2u\n", - (*iEdge).from->getIndex(), - (*iEdge).to->getIndex()); + if (traces_enabled) { + std::printf("Back edge: Node %.2u --> Node %.2u\n", + (*iEdge).from->getIndex(), + (*iEdge).to->getIndex() + ); + } } // When all nodes visited, process the pending list @@ -781,7 +784,8 @@ class TauLinker : private BackEdgeDetector { void eraseRedundantTau() { TRedundantTauMap::iterator iProvider = m_redundantTaus.begin(); for (; iProvider != m_redundantTaus.end(); ++iProvider) { - printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); + if (traces_enabled) + printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); TTauPairSet& pendingTaus = iProvider->second; TTauPairSet::iterator iPendingTau = pendingTaus.begin(); @@ -792,7 +796,9 @@ class TauLinker : private BackEdgeDetector { TauNode* const redundantTau = iPendingTau->second; if (m_processedTaus.find(remainingTau) != m_processedTaus.end()) { - printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); + if (traces_enabled) + printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); + continue; } @@ -803,10 +809,12 @@ class TauLinker : private BackEdgeDetector { for ( ; iConsumer != consumers.end(); ++iConsumer) { // FIXME Could there be non-instruction nodes? if (InstructionNode* const instruction = (*iConsumer)->cast()) { - printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", - instruction->getIndex(), - redundantTau->getIndex(), - remainingTau->getIndex()); + if (traces_enabled) { + printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", + instruction->getIndex(), + redundantTau->getIndex(), + remainingTau->getIndex()); + } instruction->setTauNode(remainingTau); remainingTau->addConsumer(instruction); @@ -816,16 +824,21 @@ class TauLinker : private BackEdgeDetector { // Remove all incomings of the redundantTau TauNode::TIncomingMap::const_iterator iIncoming = redundantTau->getIncomingMap().begin(); for ( ; iIncoming != redundantTau->getIncomingMap().end(); ++iIncoming) { - printf("Redundant tau %.2u is no longer consumer of %.2u\n", - redundantTau->getIndex(), - iIncoming->first->getIndex()); + + if (traces_enabled) { + printf("Redundant tau %.2u is no longer consumer of %.2u\n", + redundantTau->getIndex(), + iIncoming->first->getIndex()); + } iIncoming->first->removeConsumer(redundantTau); } // Marking tau as processed m_processedTaus.insert(redundantTau); - printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); + + if (traces_enabled) + printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); } } @@ -836,7 +849,9 @@ class TauLinker : private BackEdgeDetector { for (; iProcessedTau != m_processedTaus.end(); ++iProcessedTau) { TauNode* const processedTau = *iProcessedTau; - printf("Erasing processed tau %.2u\n", processedTau->getIndex()); + if (traces_enabled) + printf("Erasing processed tau %.2u\n", processedTau->getIndex()); + assert(processedTau->consumers.empty()); getGraph().eraseNode(processedTau); } @@ -851,8 +866,8 @@ class TauLinker : private BackEdgeDetector { if (consumers.size() < 2) continue; - printf("Looking for consumers of Tau %.2u (total %zu)\n", - (*iProvider)->getIndex(), consumers.size()); + if (traces_enabled) + printf("Looking for consumers of Tau %.2u (total %zu)\n", (*iProvider)->getIndex(), consumers.size()); TNodeSet::iterator iConsumer1 = consumers.begin(); for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { @@ -869,8 +884,8 @@ class TauLinker : private BackEdgeDetector { continue; if (tau1->getIncomingMap() == tau2->getIncomingMap()) { - printf("Tau %.2u and %.2u may be optimized\n", - tau1->getIndex(), tau2->getIndex()); + if (traces_enabled) + printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); m_redundantTaus[*iProvider].insert(std::make_pair(tau1, tau2)); } @@ -887,10 +902,13 @@ class TauLinker : private BackEdgeDetector { m_providers.push_back(tau); - std::printf("New type: Node %u.%.2u --> Tau %.2u\n", - instruction.getDomain()->getBasicBlock()->getOffset(), - instruction.getIndex(), - tau->getIndex()); + if (traces_enabled) { + std::printf("New type: Node %u.%.2u --> Tau %.2u\n", + instruction.getDomain()->getBasicBlock()->getOffset(), + instruction.getIndex(), + tau->getIndex() + ); + } } void processPushTemporary(InstructionNode& instruction) { @@ -913,12 +931,14 @@ class TauLinker : private BackEdgeDetector { assignType->addConsumer(&instruction); instruction.setTauNode(assignType); - std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", - assignType->getIndex(), - instruction.getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); + if (traces_enabled) { + std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", + assignType->getIndex(), + instruction.getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } } else { TauNode* const current = instruction.getTauNode(); @@ -934,23 +954,27 @@ class TauLinker : private BackEdgeDetector { aggregator->addConsumer(&instruction); instruction.setTauNode(aggregator); - std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - aggregator->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); + if (traces_enabled) { + std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + current->getIndex(), + aggregator->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } } else { current->addIncoming(assignType, (*iAssignSite).byBackEdge); - std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); + if (traces_enabled) { + std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + current->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } } } } @@ -982,9 +1006,10 @@ class TauLinker : private BackEdgeDetector { } } - std::printf("Found assign site: Node %.2u, back edge: %s\n", - instruction->getIndex(), - hasBackEdge ? "yes" : "no"); + if (traces_enabled) + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + hasBackEdge ? "yes" : "no"); assignSites.push_back(TAssignSite(instruction, hasBackEdge)); return vrSkipPath; @@ -1034,6 +1059,9 @@ void ControlGraph::buildGraph() optimizer.run(); } + if (traces_enabled) + std::printf("Phase 4. Linking PushTemporary and AssignTemporary nodes\n"); + // Linking PushTemporary and AssignTemporary pairs { TauLinker linker(*this); From 12d54609f6b0c6d604608efabfd94ed621b049a2 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 12:33:21 +0600 Subject: [PATCH 025/105] Adds meta information to control graph Meta info is very useful during type analysis. It helps to make decisions based on graph structure. In future, more flags will be added. Issue: #17 --- include/analysis.h | 9 +++++++++ src/ControlGraph.cpp | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/include/analysis.h b/include/analysis.h index 5448652..b9b6ede 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -461,6 +461,13 @@ class ControlGraph { return iDomain->second; } + struct TMetaInfo { + bool hasLoops; + bool hasBackEdgeTau; + }; + + TMetaInfo& getMeta() { return m_metaInfo; } + private: ParsedMethod* m_parsedMethod; ParsedBlock* m_parsedBlock; @@ -471,6 +478,8 @@ class ControlGraph { typedef std::map TDomainMap; TDomainMap m_blocksToDomains; + + TMetaInfo m_metaInfo; }; template<> InstructionNode* ControlNode::cast(); diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index aa36222..314208d 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -748,6 +748,8 @@ class TauLinker : private BackEdgeDetector { } } + getGraph().getMeta().hasLoops = !getBackEdges().empty(); + // When all nodes visited, process the pending list TInstructionSet::iterator iNode = m_pendingNodes.begin(); for (; iNode != m_pendingNodes.end(); ++iNode) @@ -924,6 +926,9 @@ class TauLinker : private BackEdgeDetector { TauNode* const assignType = assignTemporary->getTauNode(); assert(inheritedType); + if ((*iAssignSite).byBackEdge) + getGraph().getMeta().hasBackEdgeTau = true; + if (! instruction.getTauNode()) { // FIXME Could it be that the only incoming is accessible by back edge? // Possbible scenario: push default nil, assigned later, branch up From 6c8a0698e2980c83c2b7a7fa3c35f6ceafebcc34 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 12:45:17 +0600 Subject: [PATCH 026/105] Adds core inference logic to TypeAnalyzer Issue: #17 --- include/inference.h | 46 +++++-- src/TypeAnalyzer.cpp | 302 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 310 insertions(+), 38 deletions(-) diff --git a/include/inference.h b/include/inference.h index 665aae2..9516047 100644 --- a/include/inference.h +++ b/include/inference.h @@ -252,22 +252,35 @@ class CallContext { class TypeSystem { public: - CallContext* newCallContext(TMethod* method, const Type& arguments = Type(Type::tkPolytype)); + TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(0) {} - CallContext* getCallContext(std::size_t index); + typedef TSymbol* TSelector; + CallContext* findCallContext(TSelector selector, const Type& arguments); + + CallContext* analyzeCall(TSelector selector, const Type& arguments); + ControlGraph* getControlGraph(TMethod* method); + +private: + typedef std::pair TGraphEntry; + typedef std::map TGraphCache; + + typedef std::map TContextMap; + typedef std::map TContextCache; + +private: + SmalltalkVM& m_vm; // TODO Image must be enough + + TGraphCache m_graphCache; + TContextCache m_contextCache; + std::size_t m_lastContextIndex; }; class TypeAnalyzer { public: - TypeAnalyzer(ControlGraph& graph, CallContext& context) - : m_graph(graph), m_context(context), m_walker(*this) {} + TypeAnalyzer(TypeSystem& system, ControlGraph& graph, CallContext& context) + : m_system(system), m_graph(graph), m_context(context), m_walker(*this) {} - void run() { - if (m_graph.isEmpty()) - return; - - m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); - } + void run(); private: void processInstruction(const InstructionNode& instruction); @@ -275,12 +288,19 @@ class TypeAnalyzer { void processTau(const TauNode& tau); void walkComplete(); - void doMarkArguments(const InstructionNode& instruction); void doPushConstant(const InstructionNode& instruction); void doPushLiteral(const InstructionNode& instruction); + + void doPushTemporary(const InstructionNode& instruction); + void doAssignTemporary(const InstructionNode& instruction); + void doSendUnary(const InstructionNode& instruction); void doSendBinary(const InstructionNode& instruction); + void doMarkArguments(const InstructionNode& instruction); + void doSendMessage(const InstructionNode& instruction); + void doPrimitive(const InstructionNode& instruction); + private: class Walker : public GraphWalker { @@ -315,9 +335,13 @@ class TypeAnalyzer { }; private: + TypeSystem& m_system; ControlGraph& m_graph; CallContext& m_context; Walker m_walker; + + bool m_baseRun; + bool m_literalBranch; }; } // namespace type diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 1b54798..733dcb0 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1,4 +1,5 @@ #include +#include using namespace type; @@ -52,8 +53,60 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { return stream.str(); } +void TypeAnalyzer::run() { + if (m_graph.isEmpty()) + return; + + // FIXME For correct inference we need to perform in-width traverse + + m_baseRun = true; + m_literalBranch = true; + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + + std::cout << "Base run:" << std::endl; + type::TTypeList::const_iterator iType = m_context.getTypeList().begin(); + for (; iType != m_context.getTypeList().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + // If single return is detected, replace composite with it's subtype + // TODO We may need to store actual receiver class somewhere + Type& returnType = m_context.getReturnType(); + const bool singleReturn = (returnType.getSubTypes().size() == 1); + + if (singleReturn) + returnType = returnType[0]; + else + returnType.setKind(Type::tkComposite); + + std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; + + if (m_literalBranch && singleReturn) { + std::cout << "Return path was inferred literally. No need to perform induction run." << std::endl; + return; + } + + if (m_graph.getMeta().hasBackEdgeTau) { + m_baseRun = false; + + m_walker.resetStopNodes(); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + + std::cout << "Induction run:" << std::endl; + type::TTypeList::const_iterator iType = m_context.getTypeList().begin(); + for (; iType != m_context.getTypeList().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + if (returnType.getSubTypes().size() == 1) + returnType = returnType[0]; + else + returnType.setKind(Type::tkComposite); + + std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; + } +} + void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { - std::printf("processing %.2u\n", instruction.getIndex()); +// std::printf("processing %.2u\n", instruction.getIndex()); const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); @@ -62,39 +115,35 @@ void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { m_context[instruction] = m_context.getArgument(argument); break; - case opcode::pushConstant: doPushConstant(instruction); break; - case opcode::pushLiteral: doPushLiteral(instruction); break; - case opcode::markArguments: doMarkArguments(instruction); break; + case opcode::pushConstant: doPushConstant(instruction); break; + case opcode::pushLiteral: doPushLiteral(instruction); break; + case opcode::markArguments: doMarkArguments(instruction); break; - case opcode::sendUnary: doSendUnary(instruction); break; - case opcode::sendBinary: doSendBinary(instruction); break; + case opcode::sendUnary: doSendUnary(instruction); break; + case opcode::sendBinary: doSendBinary(instruction); break; - case opcode::assignTemporary: - m_context[instruction.getTauNode()->getIndex()] = m_context[*instruction.getArgument()]; - break; + case opcode::pushTemporary: doPushTemporary(instruction); break; + case opcode::assignTemporary: doAssignTemporary(instruction); break; - case opcode::sendMessage: - // For now, treat method call as * - m_context[instruction] = Type(Type::tkPolytype); - break; - - case opcode::doPrimitive: - m_context.getReturnType().addSubType(Type(Type::tkPolytype)); - break; + case opcode::sendMessage: doSendMessage(instruction); break; + case opcode::doPrimitive: doPrimitive(instruction); break; case opcode::doSpecial: { switch (argument) { case special::branchIfFalse: case special::branchIfTrue: { const bool branchIfTrue = (argument == special::branchIfTrue); - const Type& argType = m_context[*instruction.getArgument()]; + const BranchNode* const branch = instruction.cast(); + assert(branch); if (argType.getValue() == globals.trueObject || argType.getValue() == globals.trueObject->getClass()) m_walker.addStopNode(branchIfTrue ? branch->getSkipNode() : branch->getTargetNode()); else if (argType.getValue() == globals.falseObject || argType.getValue() == globals.falseObject->getClass()) m_walker.addStopNode(branchIfTrue ? branch->getTargetNode() : branch->getSkipNode()); + else + m_literalBranch = false; break; } @@ -178,13 +227,13 @@ void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { case Type::tkArray: { // TODO Repeat the procedure over each subtype - result.setKind(Type::tkPolytype); + result = Type(Type::tkPolytype); break; } default: - // * isNil = (Boolean) - // * notNil = (Boolean) + // * isNil -> (Boolean) + // * notNil -> (Boolean) result.set(globals.trueObject->getClass()->parentClass); } @@ -198,8 +247,11 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { Type& result = m_context[instruction]; if (isSmallInteger(type1.getValue()) && isSmallInteger(type2.getValue())) { - const int32_t rightOperand = TInteger(type2.getValue()); + if (!m_baseRun) + return; + const int32_t leftOperand = TInteger(type1.getValue()); + const int32_t rightOperand = TInteger(type2.getValue()); switch (opcode) { case binaryBuiltIns::operatorLess: @@ -229,12 +281,12 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { switch (opcode) { case binaryBuiltIns::operatorLess: case binaryBuiltIns::operatorLessOrEq: - // (SmallInt) <= (SmallInt) = (Boolean) + // (SmallInt) <= (SmallInt) -> (Boolean) result.set(globals.trueObject->getClass()->parentClass); break; case binaryBuiltIns::operatorPlus: - // (SmallInt) + (SmallInt) = (SmallInt) + // (SmallInt) + (SmallInt) -> (SmallInt) result.set(globals.smallIntClass); break; @@ -248,7 +300,7 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { // TODO In case of complex invocation encode resursive analysis of operator as a message - result.setKind(Type::tkPolytype); + result = Type(Type::tkPolytype); } void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { @@ -262,6 +314,74 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { result.set(globals.arrayClass, Type::tkArray); } +void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { + if (const TauNode* const tau = instruction.getTauNode()) { + const Type& tauType = m_context[*tau]; + + //if (tauType.getKind() == Type::tkUndefined) + if (tau->getKind() == TauNode::tkAggregator) + processTau(*tau); + + m_context[instruction] = tauType; + } +} + +void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { + if (const TauNode* const tau = instruction.getTauNode()) { + if (tau->getKind() == TauNode::tkProvider) { + m_context[*tau] = m_context[*instruction.getArgument()]; + } + } +} + +void TypeAnalyzer::doSendMessage(const InstructionNode& instruction) { + TSymbolArray& literals = *m_graph.getParsedMethod()->getOrigin()->literals; + const uint32_t literalIndex = instruction.getInstruction().getArgument(); + + TSymbol* const selector = literals[literalIndex]; + const Type& arguments = m_context[*instruction.getArgument()]; + + if (CallContext* const context = m_system.analyzeCall(selector, arguments)) { + m_context[instruction] = context->getReturnType(); + } else { + m_context[instruction] = Type(Type::tkPolytype); + } +} + +void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { + const uint8_t opcode = instruction.getInstruction().getExtra(); + + Type primitiveResult; + + switch (opcode) { + case primitive::getClass: + primitiveResult = m_context[*instruction.getArgument()]; + break; + + case primitive::smallIntSub: { + const Type& self = m_context[*instruction.getArgument(0)]; + const Type& arg = m_context[*instruction.getArgument(1)]; + + if (isSmallInteger(self.getValue()) && isSmallInteger(arg.getValue())) { + const int lhs = TInteger(self.getValue()).getValue(); + const int rhs = TInteger(arg.getValue()).getValue(); + + primitiveResult = Type(TInteger(lhs - rhs)); + } else { + // TODO Check for (SmallInt) + primitiveResult = Type(); + } + + break; + } + } + + m_context.getReturnType().addSubType(primitiveResult); + + // This should depend on the primitive inference outcome + m_walker.addStopNode(*instruction.getOutEdges().begin()); +} + void TypeAnalyzer::processPhi(const PhiNode& phi) { Type& result = m_context[phi]; @@ -275,9 +395,137 @@ void TypeAnalyzer::processPhi(const PhiNode& phi) { void TypeAnalyzer::processTau(const TauNode& tau) { Type& result = m_context[tau]; - result.setKind(Type::tkPolytype); + const TauNode::TIncomingMap& incomings = tau.getIncomingMap(); + TauNode::TIncomingMap::const_iterator iNode = incomings.begin(); + + bool typeAssigned = false; + + for (; iNode != incomings.end(); ++iNode) { + const bool byBackEdge = iNode->second; + if (byBackEdge && m_baseRun) + continue; + + std::cout << "Adding subtype to " << tau.getIndex() << " : " << m_context[*iNode->first].toString() << std::endl; + + if (! typeAssigned) { + result = m_context[*iNode->first]; + typeAssigned = true; + } else { + result &= m_context[*iNode->first]; + } + } } void TypeAnalyzer::walkComplete() { - std::printf("walk complete\n"); +// std::printf("walk complete\n"); +} + +CallContext* TypeSystem::findCallContext(TSelector selector, const Type& arguments) { + assert(selector); + + if (arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) + return 0; + + if (arguments[0].getKind() == Type::tkUndefined || arguments[0].getKind() == Type::tkPolytype) + return 0; + + const TContextCache::iterator iMap = m_contextCache.find(selector); + if (iMap == m_contextCache.end()) + return 0; + + const TContextMap::iterator iContext = iMap->second.find(arguments); + if (iContext == iMap->second.end()) + return 0; + + return iContext->second; +} + +ControlGraph* TypeSystem::getControlGraph(TMethod* method) { + TGraphCache::iterator iGraph = m_graphCache.find(method); + if (iGraph != m_graphCache.end()) + return iGraph->second.second; + + ParsedMethod* const parsedMethod = new ParsedMethod(method); + ControlGraph* const controlGraph = new ControlGraph(parsedMethod); + controlGraph->buildGraph(); + + TGraphEntry& entry = m_graphCache[method]; + entry.first = parsedMethod; + entry.second = controlGraph; + + { + std::ostringstream ss; + ss << method->klass->name->toString() + ">>" + method->name->toString(); + + ControlGraphVisualizer vis(controlGraph, ss.str(), "dots/"); + vis.run(); + } + + return controlGraph; +} + +CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) { + if (!selector || arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) + return 0; + + const Type& self = arguments[0]; + + switch (self.getKind()) { + case Type::tkUndefined: + case Type::tkPolytype: + return 0; + + // TODO Handle the case when self is a composite type + case Type::tkComposite: + return 0; + + case Type::tkLiteral: + case Type::tkMonotype: + case Type::tkArray: + break; + } + + TContextMap& contextMap = m_contextCache[selector]; + + const TContextMap::iterator iContext = contextMap.find(arguments); + if (iContext != contextMap.end()) + return iContext->second; + + TClass* receiver = 0; + + if (self.getKind() == Type::tkLiteral) { + receiver = isSmallInteger(self.getValue()) ? globals.smallIntClass : self.getValue()->getClass(); + } else { + receiver = self.getValue()->cast(); + } + + TMethod* const method = m_vm.lookupMethod(selector, receiver); + + if (! method) // TODO Redirect to #doesNotUnderstand: statically + return 0; + + CallContext* const callContext = new CallContext(m_lastContextIndex++, arguments); + contextMap[arguments] = callContext; + + ControlGraph* const controlGraph = getControlGraph(method); + assert(controlGraph); + + std::printf("Analyzing %s::%s>>%s...\n", + arguments.toString().c_str(), + receiver->name->toString().c_str(), + selector->toString().c_str()); + + // TODO Handle recursive and tail calls + type::TypeAnalyzer analyzer(*this, *controlGraph, *callContext); + analyzer.run(); + + Type& returnType = callContext->getReturnType(); + + std::printf("%s::%s>>%s -> %s\n", + arguments.toString().c_str(), + receiver->name->toString().c_str(), + selector->toString().c_str(), + returnType.toString().c_str()); + + return callContext; } From 3adc4dc9c84ec366e38fc8fe3c746c1f113fe84a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 18:32:00 +0600 Subject: [PATCH 027/105] Fixes asserts --- src/ControlGraph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 314208d..b6045d0 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -854,7 +854,7 @@ class TauLinker : private BackEdgeDetector { if (traces_enabled) printf("Erasing processed tau %.2u\n", processedTau->getIndex()); - assert(processedTau->consumers.empty()); + assert(processedTau->getIncomingMap().empty()); getGraph().eraseNode(processedTau); } @@ -924,7 +924,7 @@ class TauLinker : private BackEdgeDetector { assert(assignTemporary); TauNode* const assignType = assignTemporary->getTauNode(); - assert(inheritedType); + assert(assignType); if ((*iAssignSite).byBackEdge) getGraph().getMeta().hasBackEdgeTau = true; From 4a921d92a0c073513386b4973874c40c36496db0 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 21 May 2016 23:41:00 +0600 Subject: [PATCH 028/105] Adds more inference logic --- include/inference.h | 30 ++++---- src/TypeAnalyzer.cpp | 169 ++++++++++++++++++++++++++++--------------- 2 files changed, 123 insertions(+), 76 deletions(-) diff --git a/include/inference.h b/include/inference.h index 9516047..4c5cf05 100644 --- a/include/inference.h +++ b/include/inference.h @@ -61,8 +61,8 @@ class Type { const TSubTypes& getSubTypes() const { return m_subTypes; } - void addSubType(const Type& type) { - if (std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) + void addSubType(const Type& type, bool compact = true) { + if (!compact || std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) m_subTypes.push_back(type); } @@ -114,6 +114,7 @@ class Type { if (*this == other) return *this; + // FIXME May lose information if (m_kind == tkUndefined) return *this = other; @@ -284,8 +285,11 @@ class TypeAnalyzer { private: void processInstruction(const InstructionNode& instruction); - void processPhi(const PhiNode& phi); void processTau(const TauNode& tau); + + Type& processPhi(const PhiNode& phi); + Type& getArgumentType(const InstructionNode& instruction, std::size_t index = 0); + void walkComplete(); void doPushConstant(const InstructionNode& instruction); @@ -294,12 +298,15 @@ class TypeAnalyzer { void doPushTemporary(const InstructionNode& instruction); void doAssignTemporary(const InstructionNode& instruction); + void doPushBlock(const InstructionNode& instruction); + void doSendUnary(const InstructionNode& instruction); void doSendBinary(const InstructionNode& instruction); - void doMarkArguments(const InstructionNode& instruction); void doSendMessage(const InstructionNode& instruction); + void doPrimitive(const InstructionNode& instruction); + void doSpecial(const InstructionNode& instruction); private: @@ -309,19 +316,8 @@ class TypeAnalyzer { private: TVisitResult visitNode(ControlNode& node, const TPathNode*) { - switch (node.getNodeType()) { - case ControlNode::ntInstruction: - analyzer.processInstruction(static_cast(node)); - break; - - case ControlNode::ntPhi: - analyzer.processPhi(static_cast(node)); - break; - - case ControlNode::ntTau: - analyzer.processTau(static_cast(node)); - break; - } + if (const InstructionNode* const instruction = node.cast()) + analyzer.processInstruction(*instruction); return vrKeepWalking; } diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 733dcb0..23252f3 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -105,6 +105,16 @@ void TypeAnalyzer::run() { } } +Type& TypeAnalyzer::getArgumentType(const InstructionNode& instruction, std::size_t index /*= 0*/) { + ControlNode* const argNode = instruction.getArgument(index); + Type& result = m_context[*argNode]; + + if (PhiNode* const phi = argNode->cast()) + result = processPhi(*phi); + + return result; +} + void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { // std::printf("processing %.2u\n", instruction.getIndex()); @@ -117,58 +127,21 @@ void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { case opcode::pushConstant: doPushConstant(instruction); break; case opcode::pushLiteral: doPushLiteral(instruction); break; - case opcode::markArguments: doMarkArguments(instruction); break; - - case opcode::sendUnary: doSendUnary(instruction); break; - case opcode::sendBinary: doSendBinary(instruction); break; case opcode::pushTemporary: doPushTemporary(instruction); break; case opcode::assignTemporary: doAssignTemporary(instruction); break; + case opcode::pushBlock: doPushBlock(instruction); break; + + case opcode::markArguments: doMarkArguments(instruction); break; + case opcode::sendUnary: doSendUnary(instruction); break; + case opcode::sendBinary: doSendBinary(instruction); break; case opcode::sendMessage: doSendMessage(instruction); break; - case opcode::doPrimitive: doPrimitive(instruction); break; - case opcode::doSpecial: { - switch (argument) { - case special::branchIfFalse: - case special::branchIfTrue: { - const bool branchIfTrue = (argument == special::branchIfTrue); - const Type& argType = m_context[*instruction.getArgument()]; - - const BranchNode* const branch = instruction.cast(); - assert(branch); - - if (argType.getValue() == globals.trueObject || argType.getValue() == globals.trueObject->getClass()) - m_walker.addStopNode(branchIfTrue ? branch->getSkipNode() : branch->getTargetNode()); - else if (argType.getValue() == globals.falseObject || argType.getValue() == globals.falseObject->getClass()) - m_walker.addStopNode(branchIfTrue ? branch->getTargetNode() : branch->getSkipNode()); - else - m_literalBranch = false; - - break; - } - - case special::stackReturn: - m_context.getReturnType().addSubType(m_context[*instruction.getArgument()]); - break; - - case special::selfReturn: - m_context.getReturnType().addSubType(m_context.getArgument(0)); - break; - - case special::sendToSuper: - // For now, treat method call as * - m_context[instruction] = Type(Type::tkPolytype); - break; - - case special::duplicate: - m_context[instruction] = m_context[*instruction.getArgument()]; - break; - } - break; - } + case opcode::doPrimitive: doPrimitive(instruction); break; + case opcode::doSpecial: doSpecial(instruction); break; default: break; @@ -298,17 +271,28 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { return; } - // TODO In case of complex invocation encode resursive analysis of operator as a message + // In case of complex invocation encode resursive analysis of operator as a message + TSymbol* const selector = globals.binaryMessages[opcode]->cast(); - result = Type(Type::tkPolytype); + Type arguments(Type::tkArray); + arguments.addSubType(type1); // lhs + arguments.addSubType(type2); // rhs + + if (CallContext* const context = m_system.analyzeCall(selector, arguments)) + result = context->getReturnType(); + else + result = Type(Type::tkPolytype); } void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { Type& result = m_context[instruction]; + if (!m_baseRun) + result.reset(); + for (std::size_t index = 0; index < instruction.getArgumentsCount(); index++) { - const Type& argsType = m_context[*instruction.getArgument(index)]; - result.addSubType(argsType); + const Type& argument = m_context[*instruction.getArgument(index)]; + result.addSubType(argument, false); } result.set(globals.arrayClass, Type::tkArray); @@ -326,6 +310,10 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { } } +void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { + m_context[instruction] = Type(globals.blockClass, Type::tkLiteral); +} + void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { if (const TauNode* const tau = instruction.getTauNode()) { if (tau->getKind() == TauNode::tkProvider) { @@ -341,11 +329,11 @@ void TypeAnalyzer::doSendMessage(const InstructionNode& instruction) { TSymbol* const selector = literals[literalIndex]; const Type& arguments = m_context[*instruction.getArgument()]; - if (CallContext* const context = m_system.analyzeCall(selector, arguments)) { - m_context[instruction] = context->getReturnType(); - } else { - m_context[instruction] = Type(Type::tkPolytype); - } + Type& result = m_context[instruction]; + if (CallContext* const context = m_system.analyzeCall(selector, arguments)) + result = context->getReturnType(); + else + result = Type(Type::tkPolytype); } void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { @@ -358,6 +346,24 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { primitiveResult = m_context[*instruction.getArgument()]; break; + case primitive::getSize: { + const Type& self = m_context[*instruction.getArgument(0)]; + + if (self.getKind() == Type::tkLiteral) { + TObject* const value = self.getValue(); + const std::size_t size = isSmallInteger(value) ? 0 : value->getSize(); + + primitiveResult = Type(TInteger(size)); + } else { + primitiveResult = Type(globals.smallIntClass); + } + + // TODO What about Monotype and TCLass::instanceSize? + // Will not work for binary objects. + + break; + } + case primitive::smallIntSub: { const Type& self = m_context[*instruction.getArgument(0)]; const Type& arg = m_context[*instruction.getArgument(1)]; @@ -382,15 +388,60 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { m_walker.addStopNode(*instruction.getOutEdges().begin()); } -void TypeAnalyzer::processPhi(const PhiNode& phi) { +void TypeAnalyzer::doSpecial(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); + + switch (argument) { + case special::branchIfFalse: + case special::branchIfTrue: { + const bool branchIfTrue = (argument == special::branchIfTrue); + const Type& argType = m_context[*instruction.getArgument()]; + + const BranchNode* const branch = instruction.cast(); + assert(branch); + + if (argType.getValue() == globals.trueObject || argType.getValue() == globals.trueObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getSkipNode() : branch->getTargetNode()); + else if (argType.getValue() == globals.falseObject || argType.getValue() == globals.falseObject->getClass()) + m_walker.addStopNode(branchIfTrue ? branch->getTargetNode() : branch->getSkipNode()); + else + m_literalBranch = false; + + break; + } + + case special::stackReturn: + m_context.getReturnType().addSubType(getArgumentType(instruction)); + break; + + case special::selfReturn: + m_context.getReturnType().addSubType(m_context.getArgument(0)); + break; + + case special::sendToSuper: + // For now, treat method call as * + m_context[instruction] = Type(Type::tkPolytype); + break; + + case special::duplicate: + m_context[instruction] = m_context[*instruction.getArgument()]; + break; + } +} + +Type& TypeAnalyzer::processPhi(const PhiNode& phi) { Type& result = m_context[phi]; const TNodeSet& incomings = phi.getRealValues(); TNodeSet::iterator iNode = incomings.begin(); - for (; iNode != incomings.end(); ++iNode) - result.addSubType(m_context[*(*iNode)->cast()]); + for (; iNode != incomings.end(); ++iNode) { + // FIXME We need to track the source of the phi's incoming. + // We may ignore tkUndefined only if node lies on the dead path. + + result |= m_context[*(*iNode)->cast()]; + } - result.setKind(Type::tkComposite); + return result; } void TypeAnalyzer::processTau(const TauNode& tau) { @@ -512,7 +563,7 @@ CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) std::printf("Analyzing %s::%s>>%s...\n", arguments.toString().c_str(), - receiver->name->toString().c_str(), + method->klass->name->toString().c_str(), selector->toString().c_str()); // TODO Handle recursive and tail calls @@ -523,7 +574,7 @@ CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) std::printf("%s::%s>>%s -> %s\n", arguments.toString().c_str(), - receiver->name->toString().c_str(), + method->klass->name->toString().c_str(), selector->toString().c_str(), returnType.toString().c_str()); From d53e50de2ae18d9d39e208346df0c32d6033489d Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 24 May 2016 00:11:10 +0600 Subject: [PATCH 029/105] Adds inference for instantiation and get class primitives Issue: #17 --- src/TypeAnalyzer.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 23252f3..ea73421 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -25,6 +25,8 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { stream << "#" << getValue()->cast()->toString(); else if (getValue()->getClass()->name->toString().find("Meta", 0, 4) != std::string::npos) stream << getValue()->cast()->name->toString(); + else if (getValue()->getClass() == globals.stringClass->getClass()->getClass()) + stream << getValue()->cast()->name->toString(); else stream << "~" << getValue()->getClass()->name->toString(); break; @@ -342,9 +344,64 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { Type primitiveResult; switch (opcode) { - case primitive::getClass: - primitiveResult = m_context[*instruction.getArgument()]; + case primitive::allocateObject: + case primitive::allocateByteArray: + { + const Type& klassType = m_context[*instruction.getArgument()]; + + // instance <- Class new + // + // <7 Array 2> -> Array[nil, nil] + // <7 Object 2> -> Object[nil, nil] + // <7 Object (SmallInt)> -> (Object) + // <7 Object *> -> (Object) + // <7 (Class) 2> -> *[nil, nil] -> * + // <7 * 2> -> *[nil, nil] -> * + // <7 * (SmallInt)> -> * + // <7 * *> -> * + + switch (klassType.getKind()) { + case Type::tkLiteral: + // If we literally know the class we may define the instance's type + primitiveResult = Type(klassType.getValue(), Type::tkMonotype); + break; + + default: + // Otherwise it's completely unknown what will be the instance's type + primitiveResult = Type(Type::tkPolytype); + } + + break; + } + + case primitive::getClass: { + const Type& selfType = m_context[*instruction.getArgument()]; + TObject* const self = selfType.getValue(); + + switch (selfType.getKind()) { + case Type::tkLiteral: { + // Here class itself is a literal, not a monotype + TClass* selfClass = isSmallInteger(self) ? globals.smallIntClass : self->getClass(); + primitiveResult = Type(selfClass, Type::tkLiteral); + break; + } + + case Type::tkMonotype: + // (Object) class -> Object + primitiveResult = Type(self); + break; + + default: { + // String -> MetaString -> Class + // String class class = Class + // TClass* const classClass = globals.stringClass->getClass()->getClass(); + // primitiveResult = Type(classClass, Type::tkMonotype); + primitiveResult = Type(Type::tkPolytype); + } + } + break; + } case primitive::getSize: { const Type& self = m_context[*instruction.getArgument(0)]; From 998d38a58fb302f4034acf8a343a9989f2b70938 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 24 May 2016 00:15:19 +0600 Subject: [PATCH 030/105] Adds temporary solution for several SmallInt primitives This code need to be refactored properly. In case if both operands are literal, then result may be defined as literal too. Otherwise primitive should "fail" by allowing control flow to pass further. For literal calculation it is best to use existing code for software VM. Issue: #17 --- src/TypeAnalyzer.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index ea73421..7131434 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -421,7 +421,10 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { break; } - case primitive::smallIntSub: { + case primitive::smallIntSub: + case primitive::smallIntDiv: + case primitive::smallIntMod: + { const Type& self = m_context[*instruction.getArgument(0)]; const Type& arg = m_context[*instruction.getArgument(1)]; @@ -429,10 +432,14 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { const int lhs = TInteger(self.getValue()).getValue(); const int rhs = TInteger(arg.getValue()).getValue(); - primitiveResult = Type(TInteger(lhs - rhs)); + switch (opcode) { + case primitive::smallIntSub: primitiveResult = Type(TInteger(lhs - rhs)); break; + case primitive::smallIntDiv: primitiveResult = Type(TInteger(lhs / rhs)); break; + case primitive::smallIntMod: primitiveResult = Type(TInteger(lhs % rhs)); break; + } } else { // TODO Check for (SmallInt) - primitiveResult = Type(); + primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); } break; From 752e88efa61298051a892364ac178eb77a400520 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:17:38 +0600 Subject: [PATCH 031/105] Adds function to print block type Issue: #17 --- src/TypeAnalyzer.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 7131434..2d84363 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -3,6 +3,22 @@ using namespace type; +static void printBlock(const Type& blockType, std::stringstream& stream) { + if (blockType.getSubTypes().size() < 2) { + stream << "(Block)"; + return; + } + + TMethod* const method = blockType.getSubTypes()[0].getValue()->cast(); + const uint16_t offset = TInteger(blockType.getSubTypes()[1].getValue()); + + // Class>>method@offset + stream + << method->klass->name->toString() + << ">>" << method->name->toString() + << "@" << offset; +} + std::string Type::toString(bool subtypesOnly /*= false*/) const { std::stringstream stream; @@ -32,7 +48,10 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { break; case tkMonotype: - stream << "(" << getValue()->cast()->name->toString() << ")"; + if (getValue() == globals.blockClass) + printBlock(*this, stream); + else + stream << "(" << getValue()->cast()->name->toString() << ")"; break; case tkArray: From 4e40ce8370e5c95ca67b320d93ba76531fc5628a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:20:44 +0600 Subject: [PATCH 032/105] Renames CallContext to InferContext, analyzeCall to inferMessage Issue: #17 --- include/inference.h | 13 +++++++------ src/TypeAnalyzer.cpp | 36 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/include/inference.h b/include/inference.h index 4c5cf05..b295c38 100644 --- a/include/inference.h +++ b/include/inference.h @@ -220,9 +220,9 @@ class Type { typedef std::size_t TNodeIndex; typedef std::map TTypeList; -class CallContext { +class InferContext { public: - CallContext(std::size_t index, const Type& arguments) + InferContext(std::size_t index, const Type& arguments) : m_index(index), m_arguments(arguments) {} std::size_t getIndex() const { return m_index; } @@ -258,14 +258,15 @@ class TypeSystem { typedef TSymbol* TSelector; CallContext* findCallContext(TSelector selector, const Type& arguments); - CallContext* analyzeCall(TSelector selector, const Type& arguments); + InferContext* inferMessage(TSelector selector, const Type& arguments); + ControlGraph* getControlGraph(TMethod* method); private: typedef std::pair TGraphEntry; typedef std::map TGraphCache; - typedef std::map TContextMap; + typedef std::map TContextMap; typedef std::map TContextCache; private: @@ -278,7 +279,7 @@ class TypeSystem { class TypeAnalyzer { public: - TypeAnalyzer(TypeSystem& system, ControlGraph& graph, CallContext& context) + TypeAnalyzer(TypeSystem& system, ControlGraph& graph, InferContext& context) : m_system(system), m_graph(graph), m_context(context), m_walker(*this) {} void run(); @@ -333,7 +334,7 @@ class TypeAnalyzer { private: TypeSystem& m_system; ControlGraph& m_graph; - CallContext& m_context; + InferContext& m_context; Walker m_walker; bool m_baseRun; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 2d84363..6bf7397 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -234,18 +234,18 @@ void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { } void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { - const Type& type1 = m_context[*instruction.getArgument(0)]; - const Type& type2 = m_context[*instruction.getArgument(1)]; + const Type& lhsType = m_context[*instruction.getArgument(0)]; + const Type& rhsType = m_context[*instruction.getArgument(1)]; const binaryBuiltIns::Operator opcode = static_cast(instruction.getInstruction().getArgument()); Type& result = m_context[instruction]; - if (isSmallInteger(type1.getValue()) && isSmallInteger(type2.getValue())) { + if (isSmallInteger(lhsType.getValue()) && isSmallInteger(rhsType.getValue())) { if (!m_baseRun) return; - const int32_t leftOperand = TInteger(type1.getValue()); - const int32_t rightOperand = TInteger(type2.getValue()); + const int32_t leftOperand = TInteger(lhsType.getValue()); + const int32_t rightOperand = TInteger(rhsType.getValue()); switch (opcode) { case binaryBuiltIns::operatorLess: @@ -268,8 +268,8 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { } // Literal int or (SmallInt) monotype - const bool isInt1 = isSmallInteger(type1.getValue()) || type1.getValue() == globals.smallIntClass; - const bool isInt2 = isSmallInteger(type2.getValue()) || type2.getValue() == globals.smallIntClass; + const bool isInt1 = isSmallInteger(lhsType.getValue()) || lhsType.getValue() == globals.smallIntClass; + const bool isInt2 = isSmallInteger(rhsType.getValue()) || rhsType.getValue() == globals.smallIntClass; if (isInt1 && isInt2) { switch (opcode) { @@ -296,10 +296,10 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { TSymbol* const selector = globals.binaryMessages[opcode]->cast(); Type arguments(Type::tkArray); - arguments.addSubType(type1); // lhs - arguments.addSubType(type2); // rhs + arguments.addSubType(lhsType); + arguments.addSubType(rhsType); - if (CallContext* const context = m_system.analyzeCall(selector, arguments)) + if (InferContext* const context = m_system.inferMessage(selector, arguments)) result = context->getReturnType(); else result = Type(Type::tkPolytype); @@ -351,7 +351,7 @@ void TypeAnalyzer::doSendMessage(const InstructionNode& instruction) { const Type& arguments = m_context[*instruction.getArgument()]; Type& result = m_context[instruction]; - if (CallContext* const context = m_system.analyzeCall(selector, arguments)) + if (InferContext* const context = m_system.inferMessage(selector, arguments)) result = context->getReturnType(); else result = Type(Type::tkPolytype); @@ -598,7 +598,7 @@ ControlGraph* TypeSystem::getControlGraph(TMethod* method) { return controlGraph; } -CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) { +InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments) { if (!selector || arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) return 0; @@ -638,10 +638,10 @@ CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) if (! method) // TODO Redirect to #doesNotUnderstand: statically return 0; - CallContext* const callContext = new CallContext(m_lastContextIndex++, arguments); - contextMap[arguments] = callContext; + InferContext* const inferContext = new InferContext(m_lastContextIndex++, arguments); + contextMap[arguments] = inferContext; - ControlGraph* const controlGraph = getControlGraph(method); + ControlGraph* const methodGraph = getControlGraph(method); assert(controlGraph); std::printf("Analyzing %s::%s>>%s...\n", @@ -650,10 +650,10 @@ CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) selector->toString().c_str()); // TODO Handle recursive and tail calls - type::TypeAnalyzer analyzer(*this, *controlGraph, *callContext); + type::TypeAnalyzer analyzer(*this, *methodGraph, *inferContext); analyzer.run(); - Type& returnType = callContext->getReturnType(); + Type& returnType = inferContext->getReturnType(); std::printf("%s::%s>>%s -> %s\n", arguments.toString().c_str(), @@ -661,5 +661,5 @@ CallContext* TypeSystem::analyzeCall(TSelector selector, const Type& arguments) selector->toString().c_str(), returnType.toString().c_str()); - return callContext; + return inferContext; } From f83fa6adb8a3d7b9ed65ace49e3e4fa03ab6e5f3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:21:15 +0600 Subject: [PATCH 033/105] Removes findCallContext() Issue: #17 --- include/inference.h | 1 - src/TypeAnalyzer.cpp | 20 -------------------- 2 files changed, 21 deletions(-) diff --git a/include/inference.h b/include/inference.h index b295c38..9b194b2 100644 --- a/include/inference.h +++ b/include/inference.h @@ -256,7 +256,6 @@ class TypeSystem { TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(0) {} typedef TSymbol* TSelector; - CallContext* findCallContext(TSelector selector, const Type& arguments); InferContext* inferMessage(TSelector selector, const Type& arguments); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 6bf7397..64e67b1 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -554,26 +554,6 @@ void TypeAnalyzer::walkComplete() { // std::printf("walk complete\n"); } -CallContext* TypeSystem::findCallContext(TSelector selector, const Type& arguments) { - assert(selector); - - if (arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) - return 0; - - if (arguments[0].getKind() == Type::tkUndefined || arguments[0].getKind() == Type::tkPolytype) - return 0; - - const TContextCache::iterator iMap = m_contextCache.find(selector); - if (iMap == m_contextCache.end()) - return 0; - - const TContextMap::iterator iContext = iMap->second.find(arguments); - if (iContext == iMap->second.end()) - return 0; - - return iContext->second; -} - ControlGraph* TypeSystem::getControlGraph(TMethod* method) { TGraphCache::iterator iGraph = m_graphCache.find(method); if (iGraph != m_graphCache.end()) From 8c9cf68cc6d5da641437ed60e586e04470c96f9e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:22:12 +0600 Subject: [PATCH 034/105] Adds stub for block inference Issue: #17 --- include/inference.h | 12 ++++++++ src/TypeAnalyzer.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/include/inference.h b/include/inference.h index 9b194b2..b0ea2f6 100644 --- a/include/inference.h +++ b/include/inference.h @@ -251,6 +251,17 @@ class InferContext { Type m_returnType; }; +class BlockInferContext : public InferContext { +public: + BlockInferContext(std::size_t index, const Type& arguments) + : InferContext(index, arguments) {} + + Type& getBlockReturnType() { return m_blockReturnType; } + +private: + Type m_blockReturnType; +}; + class TypeSystem { public: TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(0) {} @@ -258,6 +269,7 @@ class TypeSystem { typedef TSymbol* TSelector; InferContext* inferMessage(TSelector selector, const Type& arguments); + InferContext* inferBlock(const Type& block, const Type& arguments); ControlGraph* getControlGraph(TMethod* method); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 64e67b1..f8c9b89 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -332,7 +332,17 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { } void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { - m_context[instruction] = Type(globals.blockClass, Type::tkLiteral); + if (const PushBlockNode* const pushBlock = instruction.cast()) { + TMethod* const origin = pushBlock->getParsedBlock()->getContainer()->getOrigin(); + const uint16_t offset = pushBlock->getParsedBlock()->getStartOffset(); + + // Block[origin, offset] + Type& blockType = m_context[instruction]; + + blockType.set(globals.blockClass, Type::tkMonotype); + blockType.addSubType(origin); + blockType.addSubType(Type(TInteger(offset))); + } } void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { @@ -440,6 +450,24 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { break; } + case primitive::blockInvoke: { + const Type& block = m_context[*instruction.getArgument(0)]; + const Type& arg = m_context[*instruction.getArgument(1)]; + + Type arguments(Type::tkArray); + arguments.addSubType(arg); + + if (instruction.getArgumentsCount() == 3) + arguments.addSubType(m_context[*instruction.getArgument(2)]); + + if (InferContext* invokeContext = m_system.inferBlock(block, arguments)) + primitiveResult = invokeContext->getReturnType(); + else + primitiveResult = Type(Type::tkPolytype); + + break; + } + case primitive::smallIntSub: case primitive::smallIntDiv: case primitive::smallIntMod: @@ -643,3 +671,43 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments return inferContext; } + +InferContext* TypeSystem::inferBlock(const Type& block, const Type& arguments) { + if (block.getKind() != Type::tkMonotype || arguments.getSubTypes().empty()) + return 0; + + // TODO Cache + BlockInferContext* const inferContext = new BlockInferContext(m_lastContextIndex++, arguments); + + TMethod* const method = block.getSubTypes()[0].getValue()->cast(); + const uint16_t offset = TInteger(block.getSubTypes()[1].getValue()); + + ControlGraph* const methodGraph = getControlGraph(method); + assert(controlGraph); + + std::printf("Analyzing block %s::%s ...\n", arguments.toString().c_str(), block.toString().c_str()); + + st::ParsedMethod* const parsedMethod = methodGraph->getParsedMethod(); + st::ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByOffset(offset); + + // TODO Cache + ControlGraph* const blockGraph = new ControlGraph(parsedMethod, parsedBlock); + blockGraph->buildGraph(); + + { + std::ostringstream ss; + ss << method->klass->name->toString() << ">>" << method->name->toString() << "@" << offset; + + ControlGraphVisualizer vis(blockGraph, ss.str(), "dots/"); + vis.run(); + } + + type::TypeAnalyzer analyzer(*this, *blockGraph, *inferContext); + analyzer.run(); + + std::printf("%s::%s -> %s, ^%s\n", arguments.toString().c_str(), block.toString().c_str(), + inferContext->getReturnType().toString().c_str(), + inferContext->getBlockReturnType().toString().c_str()); + + return 0; +} From ed3415a1bec416e08afe18b8187e71a89aa0dec3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:23:29 +0600 Subject: [PATCH 035/105] Adds logic to infer messages to literal classes Issue: #17 --- src/TypeAnalyzer.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f8c9b89..39f7aba 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -636,7 +636,12 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments TClass* receiver = 0; if (self.getKind() == Type::tkLiteral) { - receiver = isSmallInteger(self.getValue()) ? globals.smallIntClass : self.getValue()->getClass(); + if (isSmallInteger(self.getValue())) + receiver = globals.smallIntClass; + else if (self.getValue()->getClass()->getClass() == globals.stringClass->getClass()->getClass()) + receiver = self.getValue()->cast(); + else + receiver = self.getValue()->getClass(); } else { receiver = self.getValue()->cast(); } From d92e33f970f3223e51225bf043a7139f57001367 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 26 May 2016 00:49:38 +0600 Subject: [PATCH 036/105] Adds inference for block invocation arguments Issue: #17 --- include/inference.h | 4 +++- src/TypeAnalyzer.cpp | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/include/inference.h b/include/inference.h index b0ea2f6..dcdcf81 100644 --- a/include/inference.h +++ b/include/inference.h @@ -293,7 +293,7 @@ class TypeAnalyzer { TypeAnalyzer(TypeSystem& system, ControlGraph& graph, InferContext& context) : m_system(system), m_graph(graph), m_context(context), m_walker(*this) {} - void run(); + void run(const Type* blockType = 0); private: void processInstruction(const InstructionNode& instruction); @@ -350,6 +350,8 @@ class TypeAnalyzer { bool m_baseRun; bool m_literalBranch; + + const Type* m_blockType; }; } // namespace type diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 39f7aba..305d95a 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -74,10 +74,12 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { return stream.str(); } -void TypeAnalyzer::run() { +void TypeAnalyzer::run(const Type* blockType /*= 0*/) { if (m_graph.isEmpty()) return; + m_blockType = blockType; + // FIXME For correct inference we need to perform in-width traverse m_baseRun = true; @@ -328,6 +330,17 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { processTau(*tau); m_context[instruction] = tauType; + } else if (m_blockType) { + // Block invocation primitive pass block arguments through creating method's temporaries. + // To simplify inference, we pass their types as context arguments. + + const uint16_t argIndex = TInteger(m_blockType->getSubTypes()[2].getValue()); + const TSmalltalkInstruction::TArgument tempIndex = instruction.getInstruction().getArgument(); + + if (tempIndex >= argIndex) + m_context[instruction] = m_context.getArgument(tempIndex - argIndex); + else + m_context[instruction] = Type(Type::tkPolytype); } } @@ -335,6 +348,7 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { if (const PushBlockNode* const pushBlock = instruction.cast()) { TMethod* const origin = pushBlock->getParsedBlock()->getContainer()->getOrigin(); const uint16_t offset = pushBlock->getParsedBlock()->getStartOffset(); + const uint16_t argIndex = instruction.getInstruction().getArgument(); // Block[origin, offset] Type& blockType = m_context[instruction]; @@ -342,6 +356,7 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { blockType.set(globals.blockClass, Type::tkMonotype); blockType.addSubType(origin); blockType.addSubType(Type(TInteger(offset))); + blockType.addSubType(Type(TInteger(argIndex))); } } @@ -708,11 +723,11 @@ InferContext* TypeSystem::inferBlock(const Type& block, const Type& arguments) { } type::TypeAnalyzer analyzer(*this, *blockGraph, *inferContext); - analyzer.run(); + analyzer.run(&block); std::printf("%s::%s -> %s, ^%s\n", arguments.toString().c_str(), block.toString().c_str(), inferContext->getReturnType().toString().c_str(), inferContext->getBlockReturnType().toString().c_str()); - return 0; + return inferContext; } From a5bc8c558f9307c8d34d59856be0c138ed5ee9be Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 27 May 2016 00:22:46 +0600 Subject: [PATCH 037/105] Fixes subtypes in array context Issue: #17 --- src/TypeAnalyzer.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 305d95a..e1a9f18 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -298,8 +298,8 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { TSymbol* const selector = globals.binaryMessages[opcode]->cast(); Type arguments(Type::tkArray); - arguments.addSubType(lhsType); - arguments.addSubType(rhsType); + arguments.addSubType(lhsType, false); + arguments.addSubType(rhsType, false); if (InferContext* const context = m_system.inferMessage(selector, arguments)) result = context->getReturnType(); @@ -354,9 +354,9 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { Type& blockType = m_context[instruction]; blockType.set(globals.blockClass, Type::tkMonotype); - blockType.addSubType(origin); - blockType.addSubType(Type(TInteger(offset))); - blockType.addSubType(Type(TInteger(argIndex))); + blockType.addSubType(origin, false); + blockType.addSubType(Type(TInteger(offset)), false); + blockType.addSubType(Type(TInteger(argIndex)), false); } } @@ -473,7 +473,7 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { arguments.addSubType(arg); if (instruction.getArgumentsCount() == 3) - arguments.addSubType(m_context[*instruction.getArgument(2)]); + arguments.addSubType(m_context[*instruction.getArgument(2)], false); if (InferContext* invokeContext = m_system.inferBlock(block, arguments)) primitiveResult = invokeContext->getReturnType(); From b3953dbd45f3ba05564c9c4b013fbe523fa31137 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 31 May 2016 10:29:17 +0600 Subject: [PATCH 038/105] Disambiguates subtype fill functions Issue: #17 --- include/inference.h | 6 ++++-- src/TypeAnalyzer.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/inference.h b/include/inference.h index dcdcf81..e49302c 100644 --- a/include/inference.h +++ b/include/inference.h @@ -61,8 +61,10 @@ class Type { const TSubTypes& getSubTypes() const { return m_subTypes; } - void addSubType(const Type& type, bool compact = true) { - if (!compact || std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) + void pushSubType(const Type& type) { m_subTypes.push_back(type); } + + void addSubType(const Type& type) { + if (std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) m_subTypes.push_back(type); } diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index e1a9f18..df0934b 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -298,8 +298,8 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { TSymbol* const selector = globals.binaryMessages[opcode]->cast(); Type arguments(Type::tkArray); - arguments.addSubType(lhsType, false); - arguments.addSubType(rhsType, false); + arguments.pushSubType(lhsType); + arguments.pushSubType(rhsType); if (InferContext* const context = m_system.inferMessage(selector, arguments)) result = context->getReturnType(); @@ -315,7 +315,7 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { for (std::size_t index = 0; index < instruction.getArgumentsCount(); index++) { const Type& argument = m_context[*instruction.getArgument(index)]; - result.addSubType(argument, false); + result.pushSubType(argument); } result.set(globals.arrayClass, Type::tkArray); @@ -354,9 +354,9 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { Type& blockType = m_context[instruction]; blockType.set(globals.blockClass, Type::tkMonotype); - blockType.addSubType(origin, false); - blockType.addSubType(Type(TInteger(offset)), false); - blockType.addSubType(Type(TInteger(argIndex)), false); + blockType.pushSubType(origin); + blockType.pushSubType(Type(TInteger(offset))); + blockType.pushSubType(Type(TInteger(argIndex))); } } @@ -470,10 +470,10 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { const Type& arg = m_context[*instruction.getArgument(1)]; Type arguments(Type::tkArray); - arguments.addSubType(arg); + arguments.pushSubType(arg); if (instruction.getArgumentsCount() == 3) - arguments.addSubType(m_context[*instruction.getArgument(2)], false); + arguments.pushSubType(m_context[*instruction.getArgument(2)]); if (InferContext* invokeContext = m_system.inferBlock(block, arguments)) primitiveResult = invokeContext->getReturnType(); From ec073fdf82dc18fcf232e906ebf91062d5db91f3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 31 May 2016 10:30:47 +0600 Subject: [PATCH 039/105] Adds more small int primitives (still temporary solution) Issue: #17 --- src/TypeAnalyzer.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index df0934b..d971143 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -483,9 +483,13 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { break; } + case primitive::smallIntAdd: case primitive::smallIntSub: + case primitive::smallIntMul: case primitive::smallIntDiv: case primitive::smallIntMod: + case primitive::smallIntEqual: + case primitive::smallIntLess: { const Type& self = m_context[*instruction.getArgument(0)]; const Type& arg = m_context[*instruction.getArgument(1)]; @@ -495,13 +499,24 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { const int rhs = TInteger(arg.getValue()).getValue(); switch (opcode) { + case primitive::smallIntAdd: primitiveResult = Type(TInteger(lhs + rhs)); break; case primitive::smallIntSub: primitiveResult = Type(TInteger(lhs - rhs)); break; + case primitive::smallIntMul: primitiveResult = Type(TInteger(lhs * rhs)); break; case primitive::smallIntDiv: primitiveResult = Type(TInteger(lhs / rhs)); break; case primitive::smallIntMod: primitiveResult = Type(TInteger(lhs % rhs)); break; + + case primitive::smallIntEqual: + primitiveResult = (lhs == rhs) ? globals.trueObject : globals.falseObject; + break; + + case primitive::smallIntLess: + primitiveResult = (lhs < rhs) ? globals.trueObject : globals.falseObject; + break; } } else { // TODO Check for (SmallInt) - primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); + primitiveResult = Type(Type::tkPolytype); + // primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); } break; From cae29044365ac32692ed102b364831120063c2b4 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 31 May 2016 10:33:18 +0600 Subject: [PATCH 040/105] Collects graph meta info during graph construction This information is very useful during type inference. It may be gathered on-demand, but that would require additional pass. It is very cheap to collect meta info on the fly, so why not? Issue: #17 --- include/analysis.h | 25 +++++++++++++++++++++++- src/ControlGraph.cpp | 46 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index b9b6ede..4e88cf4 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -370,7 +370,10 @@ class ControlGraph { : m_parsedMethod(parsedMethod), m_parsedBlock(0), m_lastNodeIndex(0) { } ControlGraph(ParsedMethod* parsedMethod, ParsedBlock* parsedBlock) - : m_parsedMethod(parsedMethod), m_parsedBlock(parsedBlock), m_lastNodeIndex(0) { } + : m_parsedMethod(parsedMethod), m_parsedBlock(parsedBlock), m_lastNodeIndex(0) + { + m_metaInfo.isBlock = true; + } ParsedMethod* getParsedMethod() const { return m_parsedMethod; } @@ -462,8 +465,27 @@ class ControlGraph { } struct TMetaInfo { + bool isBlock; + bool hasBlockReturn; + bool hasLiteralBlocks; + bool hasLoops; bool hasBackEdgeTau; + + bool usesSelf; + bool usesSuper; + + bool readsArguments; + bool readsFields; + bool writesFields; + + bool hasPrimitive; + + typedef std::set TIndexSet; + TIndexSet readsTemporaries; + TIndexSet writesTemporaries; + + TMetaInfo(); }; TMetaInfo& getMeta() { return m_metaInfo; } @@ -492,6 +514,7 @@ template<> TauNode* ControlGraph::newNode(); template<> PushBlockNode* ControlNode::cast(); template<> PushBlockNode* ControlGraph::newNode(); +template<> const PushBlockNode* ControlNode::cast() const; template<> BranchNode* ControlNode::cast(); template<> const BranchNode* ControlNode::cast() const; diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index b6045d0..e1573d1 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -5,6 +5,21 @@ using namespace st; static const bool traces_enabled = false; +ControlGraph::TMetaInfo::TMetaInfo() : + isBlock(false), + hasBlockReturn(false), + hasLiteralBlocks(false), + hasLoops(false), + hasBackEdgeTau(false), + usesSelf(false), + usesSuper(false), + readsArguments(false), + readsFields(false), + writesFields(false), + hasPrimitive(false) +{ +} + bool NodeIndexCompare::operator() (const ControlNode* a, const ControlNode* b) const { return a->getIndex() < b->getIndex(); @@ -33,6 +48,10 @@ template<> PushBlockNode* ControlNode::cast() { return static_cast(this); } +template<> const PushBlockNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + template<> PushBlockNode* ControlGraph::newNode() { PushBlockNode* const node = new PushBlockNode(m_lastNodeIndex++); m_nodes.push_back(node); @@ -146,15 +165,24 @@ void GraphConstructor::processNode(InstructionNode* node) m_currentDomain->setEntryPoint(node); switch (instruction.getOpcode()) { - case opcode::pushConstant: - case opcode::pushLiteral: case opcode::pushArgument: - case opcode::pushTemporary: // TODO Link with tau node + if (instruction.getArgument() == 0) + m_graph->getMeta().usesSelf = true; + m_graph->getMeta().readsArguments = true; + case opcode::pushInstance: + m_graph->getMeta().readsFields = true; + + case opcode::pushTemporary: + m_graph->getMeta().readsTemporaries.insert(node->getInstruction().getArgument()); + case opcode::pushConstant: + case opcode::pushLiteral: m_currentDomain->pushValue(node); break; case opcode::pushBlock: { + m_graph->getMeta().hasLiteralBlocks = true; + const uint16_t blockEndOffset = node->getInstruction().getExtra(); ParsedMethod* const parsedMethod = m_graph->getParsedMethod(); ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByEndOffset(blockEndOffset); @@ -163,8 +191,13 @@ void GraphConstructor::processNode(InstructionNode* node) m_currentDomain->pushValue(node); } break; - case opcode::assignTemporary: // TODO Link with tau node + case opcode::assignTemporary: + m_graph->getMeta().writesTemporaries.insert(node->getInstruction().getArgument()); + m_currentDomain->requestArgument(0, node, true); + break; + case opcode::assignInstance: + m_graph->getMeta().writesFields = true; m_currentDomain->requestArgument(0, node, true); break; @@ -191,6 +224,7 @@ void GraphConstructor::processNode(InstructionNode* node) break; case opcode::doPrimitive: + m_graph->getMeta().hasPrimitive = true; processPrimitives(node); m_currentDomain->pushValue(node); break; @@ -205,8 +239,9 @@ void GraphConstructor::processSpecials(InstructionNode* node) const TSmalltalkInstruction& instruction = node->getInstruction(); switch (instruction.getArgument()) { - case special::stackReturn: case special::blockReturn: + m_graph->getMeta().hasBlockReturn = true; + case special::stackReturn: m_currentDomain->requestArgument(0, node); case special::selfReturn: @@ -215,6 +250,7 @@ void GraphConstructor::processSpecials(InstructionNode* node) break; case special::sendToSuper: + m_graph->getMeta().usesSuper = true; m_currentDomain->requestArgument(0, node); m_currentDomain->pushValue(node); break; From c7036a512c33361790bdeba9dcc21c4c8baf958d Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 31 May 2016 10:35:26 +0600 Subject: [PATCH 041/105] Fixes prototype of TypeSystem::inferBlock() Issue: #17 --- include/inference.h | 2 +- src/TypeAnalyzer.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/inference.h b/include/inference.h index e49302c..c9163b5 100644 --- a/include/inference.h +++ b/include/inference.h @@ -271,7 +271,7 @@ class TypeSystem { typedef TSymbol* TSelector; InferContext* inferMessage(TSelector selector, const Type& arguments); - InferContext* inferBlock(const Type& block, const Type& arguments); + BlockInferContext* inferBlock(const Type& block, const Type& arguments); ControlGraph* getControlGraph(TMethod* method); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index d971143..9fe01c7 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -475,7 +475,7 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { if (instruction.getArgumentsCount() == 3) arguments.pushSubType(m_context[*instruction.getArgument(2)]); - if (InferContext* invokeContext = m_system.inferBlock(block, arguments)) + if (BlockInferContext* invokeContext = m_system.inferBlock(block, arguments)) primitiveResult = invokeContext->getReturnType(); else primitiveResult = Type(Type::tkPolytype); @@ -707,7 +707,7 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments return inferContext; } -InferContext* TypeSystem::inferBlock(const Type& block, const Type& arguments) { +BlockInferContext* TypeSystem::inferBlock(const Type& block, const Type& arguments) { if (block.getKind() != Type::tkMonotype || arguments.getSubTypes().empty()) return 0; From 92cf90835e532760bd08ec27adea2c03342bae17 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 1 Jun 2016 21:04:00 +0600 Subject: [PATCH 042/105] Refactors graph metainfo std::set here is an overkill. Index arrays are at most 10 entries big, unique vector is ok here. Issue: #17 --- include/analysis.h | 11 ++++++++--- src/ControlGraph.cpp | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index 4e88cf4..d448294 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -481,9 +481,14 @@ class ControlGraph { bool hasPrimitive; - typedef std::set TIndexSet; - TIndexSet readsTemporaries; - TIndexSet writesTemporaries; + typedef std::vector TIndexList; + TIndexList readsTemporaries; + TIndexList writesTemporaries; + + static void insertIndex(std::size_t index, TIndexList& list) { + if (std::find(list.begin(), list.end(), index) == list.end()) + list.push_back(index); + } TMetaInfo(); }; diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index e1573d1..9c01142 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -174,7 +174,7 @@ void GraphConstructor::processNode(InstructionNode* node) m_graph->getMeta().readsFields = true; case opcode::pushTemporary: - m_graph->getMeta().readsTemporaries.insert(node->getInstruction().getArgument()); + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsTemporaries); case opcode::pushConstant: case opcode::pushLiteral: m_currentDomain->pushValue(node); @@ -192,7 +192,7 @@ void GraphConstructor::processNode(InstructionNode* node) } break; case opcode::assignTemporary: - m_graph->getMeta().writesTemporaries.insert(node->getInstruction().getArgument()); + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().writesTemporaries); m_currentDomain->requestArgument(0, node, true); break; From cc62ba044131ebe01ba908bd2d3af6c0da25d9cb Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 1 Jun 2016 21:05:29 +0600 Subject: [PATCH 043/105] Adds graph back edges set to metainfo Issue: #17 --- include/analysis.h | 58 ++++++++++++++++++++++++-------------------- src/ControlGraph.cpp | 1 + 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index d448294..74f3d57 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -3,6 +3,7 @@ #include #include +#include #include @@ -464,6 +465,33 @@ class ControlGraph { return iDomain->second; } + struct TEdge { + const InstructionNode* from; + const InstructionNode* to; + + TEdge(const InstructionNode* from, const InstructionNode* to) + : from(from), to(to) + { + assert(from); + assert(to); + } + }; + + class EdgeCompare { + public: + bool operator() (const TEdge& a, const TEdge& b) const { + if (a.from < b.from) + return true; + + if (a.from > b.from) + return false; + + return a.to < b.to; + } + }; + + typedef std::set TEdgeSet; + struct TMetaInfo { bool isBlock; bool hasBlockReturn; @@ -481,6 +509,8 @@ class ControlGraph { bool hasPrimitive; + TEdgeSet backEdges; + typedef std::vector TIndexList; TIndexList readsTemporaries; TIndexList writesTemporaries; @@ -746,32 +776,8 @@ class PathVerifier : public GraphWalker { class BackEdgeDetector : public GraphWalker { public: - struct TEdge { - const InstructionNode* from; - const InstructionNode* to; - - TEdge(const InstructionNode* from, const InstructionNode* to) - : from(from), to(to) - { - assert(from); - assert(to); - } - }; - - class EdgeCompare { - public: - bool operator() (const TEdge& a, const TEdge& b) const { - if (a.from < b.from) - return true; - - if (a.from > b.from) - return false; - - return a.to < b.to; - } - }; - - typedef std::set TEdgeSet; + typedef ControlGraph::TEdge TEdge; + typedef ControlGraph::TEdgeSet TEdgeSet; const TEdgeSet& getBackEdges() const { return m_backEdges; } diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 9c01142..0ca55ed 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -785,6 +785,7 @@ class TauLinker : private BackEdgeDetector { } getGraph().getMeta().hasLoops = !getBackEdges().empty(); + m_graph.getMeta().backEdges = getBackEdges(); // When all nodes visited, process the pending list TInstructionSet::iterator iNode = m_pendingNodes.begin(); From 37677b773c23bfcdab281dc756038c773f194fb3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 1 Jun 2016 21:17:37 +0600 Subject: [PATCH 044/105] Adds inference of variables from block's lexical context Issue: #17 --- include/inference.h | 12 ++- src/TypeAnalyzer.cpp | 175 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 168 insertions(+), 19 deletions(-) diff --git a/include/inference.h b/include/inference.h index c9163b5..fd0b1a1 100644 --- a/include/inference.h +++ b/include/inference.h @@ -61,7 +61,7 @@ class Type { const TSubTypes& getSubTypes() const { return m_subTypes; } - void pushSubType(const Type& type) { m_subTypes.push_back(type); } + Type& pushSubType(const Type& type) { m_subTypes.push_back(type); return m_subTypes.back(); } void addSubType(const Type& type) { if (std::find(m_subTypes.begin(), m_subTypes.end(), type) == m_subTypes.end()) @@ -69,6 +69,7 @@ class Type { } const Type& operator [] (std::size_t index) const { return m_subTypes[index]; } + Type& operator [] (std::size_t index) { return m_subTypes[index]; } bool operator < (const Type& other) const { if (m_kind != other.m_kind) @@ -298,7 +299,7 @@ class TypeAnalyzer { void run(const Type* blockType = 0); private: - void processInstruction(const InstructionNode& instruction); + void processInstruction(InstructionNode& instruction); void processTau(const TauNode& tau); Type& processPhi(const PhiNode& phi); @@ -317,11 +318,14 @@ class TypeAnalyzer { void doSendUnary(const InstructionNode& instruction); void doSendBinary(const InstructionNode& instruction); void doMarkArguments(const InstructionNode& instruction); - void doSendMessage(const InstructionNode& instruction); + void doSendMessage(InstructionNode& instruction); void doPrimitive(const InstructionNode& instruction); void doSpecial(const InstructionNode& instruction); +private: + void processBlocks(InstructionNode& instruction, Type& arguments); + private: class Walker : public GraphWalker { @@ -330,7 +334,7 @@ class TypeAnalyzer { private: TVisitResult visitNode(ControlNode& node, const TPathNode*) { - if (const InstructionNode* const instruction = node.cast()) + if (InstructionNode* const instruction = node.cast()) analyzer.processInstruction(*instruction); return vrKeepWalking; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 9fe01c7..45a7546 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -9,14 +9,19 @@ static void printBlock(const Type& blockType, std::stringstream& stream) { return; } - TMethod* const method = blockType.getSubTypes()[0].getValue()->cast(); - const uint16_t offset = TInteger(blockType.getSubTypes()[1].getValue()); + TMethod* const method = blockType[0].getValue()->cast(); + const uint16_t offset = TInteger(blockType[1].getValue()); // Class>>method@offset stream << method->klass->name->toString() << ">>" << method->name->toString() - << "@" << offset; + << "@" << offset + << blockType[3].toString() + << blockType[4].toString(); + + if (blockType.getSubTypes().size() > 5) + stream << blockType[5].toString(); } std::string Type::toString(bool subtypesOnly /*= false*/) const { @@ -138,7 +143,7 @@ Type& TypeAnalyzer::getArgumentType(const InstructionNode& instruction, std::siz return result; } -void TypeAnalyzer::processInstruction(const InstructionNode& instruction) { +void TypeAnalyzer::processInstruction(InstructionNode& instruction) { // std::printf("processing %.2u\n", instruction.getIndex()); const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); @@ -337,10 +342,24 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { const uint16_t argIndex = TInteger(m_blockType->getSubTypes()[2].getValue()); const TSmalltalkInstruction::TArgument tempIndex = instruction.getInstruction().getArgument(); - if (tempIndex >= argIndex) + if (tempIndex >= argIndex) { m_context[instruction] = m_context.getArgument(tempIndex - argIndex); - else + } else { + if (m_blockType->getSubTypes().size() > 5) { + const Type& readIndices = (*m_blockType)[3]; + const Type& contextTypes = (*m_blockType)[5]; + + for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { + if (TInteger(readIndices[i].getValue()) == tempIndex) { + m_context[instruction] = contextTypes[i]; + return; + } + } + + } + m_context[instruction] = Type(Type::tkPolytype); + } } } @@ -350,13 +369,42 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { const uint16_t offset = pushBlock->getParsedBlock()->getStartOffset(); const uint16_t argIndex = instruction.getInstruction().getArgument(); - // Block[origin, offset] Type& blockType = m_context[instruction]; blockType.set(globals.blockClass, Type::tkMonotype); - blockType.pushSubType(origin); - blockType.pushSubType(Type(TInteger(offset))); - blockType.pushSubType(Type(TInteger(argIndex))); + blockType.pushSubType(origin); // [0] + blockType.pushSubType(Type(TInteger(offset))); // [1] + blockType.pushSubType(Type(TInteger(argIndex))); // [2] + + // TODO Cache and reuse in TypeSystem::inferBlock() + ControlGraph* const blockGraph = new ControlGraph(pushBlock->getParsedBlock()->getContainer(), pushBlock->getParsedBlock()); + blockGraph->buildGraph(); + + typedef ControlGraph::TMetaInfo::TIndexList TIndexList; + const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; + const TIndexList& writesTemporaries = blockGraph->getMeta().writesTemporaries; + + Type readIndices(globals.arrayClass, Type::tkArray); + Type writeIndices(globals.arrayClass, Type::tkArray); + + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (readsTemporaries[index] >= argIndex) + continue; + + readIndices.pushSubType(Type(TInteger(readsTemporaries[index]))); + } + + for (std::size_t index = 0; index < writesTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (writesTemporaries[index] >= argIndex) + continue; + + writeIndices.pushSubType(Type(TInteger(writesTemporaries[index]))); + } + + blockType.pushSubType(readIndices); // [3] + blockType.pushSubType(writeIndices); // [4] } } @@ -368,12 +416,109 @@ void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { } } -void TypeAnalyzer::doSendMessage(const InstructionNode& instruction) { +class TypeLocator : public GraphWalker { +public: + TypeLocator(std::size_t tempIndex, const ControlGraph::TEdgeSet& backEdges, InferContext& context, bool noBackEdges) + : tempIndex(tempIndex), backEdges(backEdges), context(context), noBackEdges(noBackEdges), firstResult(true) {} + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + if (InstructionNode* const instruction = node.cast()) { + // TODO Also handle previously discovered non-trivial assign sites + + if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + if (instruction->getInstruction().getArgument() == tempIndex) { + TauNode* const tau = instruction->getTauNode(); + if (! tau) + return vrSkipPath; + + // Searching for back edges in the located path + bool hasBackEdge = false; + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != backEdges.end()) { + hasBackEdge = true; + break; + } + } + + std::printf("TypeLocator : Found assign site: Node %.2u :: %s, back edge: %s\n", + instruction->getIndex(), + context[*tau].toString().c_str(), + hasBackEdge ? "yes" : "no"); + + if (hasBackEdge && noBackEdges) + return vrSkipPath; + + if (firstResult) { + result = context[*tau]; + firstResult = false; + } else { + result &= context[*tau]; + } + + return vrSkipPath; + } + } + } + + return vrKeepWalking; + } + + const TSmalltalkInstruction::TArgument tempIndex; + const ControlGraph::TEdgeSet& backEdges; + InferContext& context; + bool noBackEdges; + + Type result; + bool firstResult; +}; + +void TypeAnalyzer::processBlocks(InstructionNode& instruction, Type& arguments) { + TMethod* const currentMethod = m_graph.getParsedMethod()->getOrigin(); + + // We interested in literal blocks with context info from the same method + for (std::size_t argIndex = 0; argIndex < arguments.getSubTypes().size(); argIndex++) { + Type& blockType = arguments[argIndex]; + + if (blockType.getValue() != globals.blockClass || blockType.getKind() != Type::tkMonotype) + continue; // Not a block we may handle + + if (blockType.getSubTypes().empty() || blockType[0].getValue() != currentMethod) + continue; // Non-literal or non-local block + + // Indexes of temporaries from the lexical context. See TypeAnalyzer::pushBlock() + const Type& readIndices = blockType[3]; + Type& temps = blockType.pushSubType(Type(globals.arrayClass, Type::tkArray)); + + for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { + // Detect types of temporaries accessible from the current call site + TypeLocator locator( + TInteger(readIndices[i].getValue()), // look for temporary with this index + m_graph.getMeta().backEdges, + m_context, + m_baseRun // base run = skip back edges + ); + + locator.run(&instruction, st::GraphWalker::wdBackward); + temps.pushSubType(locator.result); + } + } +} + +void TypeAnalyzer::doSendMessage(InstructionNode& instruction) { TSymbolArray& literals = *m_graph.getParsedMethod()->getOrigin()->literals; const uint32_t literalIndex = instruction.getInstruction().getArgument(); - TSymbol* const selector = literals[literalIndex]; - const Type& arguments = m_context[*instruction.getArgument()]; + TSymbol* const selector = literals[literalIndex]; + Type& arguments = m_context[*instruction.getArgument()]; + + processBlocks(instruction, arguments); Type& result = m_context[instruction]; if (InferContext* const context = m_system.inferMessage(selector, arguments)) @@ -714,8 +859,8 @@ BlockInferContext* TypeSystem::inferBlock(const Type& block, const Type& argumen // TODO Cache BlockInferContext* const inferContext = new BlockInferContext(m_lastContextIndex++, arguments); - TMethod* const method = block.getSubTypes()[0].getValue()->cast(); - const uint16_t offset = TInteger(block.getSubTypes()[1].getValue()); + TMethod* const method = block[0].getValue()->cast(); + const uint16_t offset = TInteger(block[1].getValue()); ControlGraph* const methodGraph = getControlGraph(method); assert(controlGraph); From c6003e648cb64df1ead8504e54c1efbcccdd41cb Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 5 Jun 2016 12:47:31 +0600 Subject: [PATCH 045/105] Adds inference of the captured context This logic provides a way to propagate captured context to the block across the call stack and collect types of the temporary variables that may be assigned in the closure block. This information will be used to update the method's control flow graph and introduce nontrivial tau nodes. Issue: #17 --- include/inference.h | 76 +++++++++++----- src/TypeAnalyzer.cpp | 204 +++++++++++++++++++++++++++---------------- 2 files changed, 181 insertions(+), 99 deletions(-) diff --git a/include/inference.h b/include/inference.h index fd0b1a1..c903929 100644 --- a/include/inference.h +++ b/include/inference.h @@ -21,6 +21,16 @@ class Type { tkPolytype }; + enum TBlockSubtypes { + bstOrigin = 0, + bstOffset, + bstArgIndex, + bstContextIndex, + bstReadsTemps, + bstWritesTemps, + bstCaptureIndex + }; + // Return a string representation of a type: // Kind Representation Example // tkUndefined ? ? @@ -225,9 +235,10 @@ typedef std::map TTypeList; class InferContext { public: - InferContext(std::size_t index, const Type& arguments) - : m_index(index), m_arguments(arguments) {} + InferContext(TMethod* method, std::size_t index, const Type& arguments) + : m_method(method), m_index(index), m_arguments(arguments) {} + TMethod* getMethod() const { return m_method; } std::size_t getIndex() const { return m_index; } const Type& getArgument(std::size_t index) const { @@ -238,6 +249,7 @@ class InferContext { else return polytype; } + const Type& getArguments() const { return m_arguments; } const TTypeList& getTypeList() const { return m_instructions; } @@ -247,22 +259,30 @@ class InferContext { Type& operator[] (TNodeIndex index) { return m_instructions[index]; } Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } + // variable index -> aggregated type + typedef std::map TTypeMap; + + // capture site index -> captured context types + typedef std::map TBlockClosures; + + TBlockClosures& getBlockClosures() { return m_blockClosures; } + private: + TMethod* const m_method; const std::size_t m_index; - const Type m_arguments; - TTypeList m_instructions; - Type m_returnType; -}; + const Type m_arguments; + TTypeList m_instructions; + Type m_returnType; -class BlockInferContext : public InferContext { -public: - BlockInferContext(std::size_t index, const Type& arguments) - : InferContext(index, arguments) {} + TBlockClosures m_blockClosures; +}; - Type& getBlockReturnType() { return m_blockReturnType; } +struct TContextStack { + InferContext& context; + TContextStack* parent; -private: - Type m_blockReturnType; + TContextStack(InferContext& context, TContextStack* parent = 0) + : context(context), parent(parent) {} }; class TypeSystem { @@ -271,8 +291,9 @@ class TypeSystem { typedef TSymbol* TSelector; - InferContext* inferMessage(TSelector selector, const Type& arguments); - BlockInferContext* inferBlock(const Type& block, const Type& arguments); + + InferContext* inferMessage(TSelector selector, const Type& arguments, TContextStack* parent); + InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); ControlGraph* getControlGraph(TMethod* method); @@ -280,7 +301,7 @@ class TypeSystem { typedef std::pair TGraphEntry; typedef std::map TGraphCache; - typedef std::map TContextMap; + typedef std::map TContextMap; typedef std::map TContextCache; private: @@ -293,8 +314,14 @@ class TypeSystem { class TypeAnalyzer { public: - TypeAnalyzer(TypeSystem& system, ControlGraph& graph, InferContext& context) - : m_system(system), m_graph(graph), m_context(context), m_walker(*this) {} + TypeAnalyzer(TypeSystem& system, ControlGraph& graph, TContextStack& contextStack) : + m_system(system), + m_graph(graph), + m_contextStack(contextStack), + m_context(contextStack.context), + m_walker(*this) + { + } void run(const Type* blockType = 0); @@ -309,6 +336,7 @@ class TypeAnalyzer { void doPushConstant(const InstructionNode& instruction); void doPushLiteral(const InstructionNode& instruction); + void doPushArgument(const InstructionNode& instruction); void doPushTemporary(const InstructionNode& instruction); void doAssignTemporary(const InstructionNode& instruction); @@ -316,7 +344,7 @@ class TypeAnalyzer { void doPushBlock(const InstructionNode& instruction); void doSendUnary(const InstructionNode& instruction); - void doSendBinary(const InstructionNode& instruction); + void doSendBinary(InstructionNode& instruction); void doMarkArguments(const InstructionNode& instruction); void doSendMessage(InstructionNode& instruction); @@ -324,7 +352,8 @@ class TypeAnalyzer { void doSpecial(const InstructionNode& instruction); private: - void processBlocks(InstructionNode& instruction, Type& arguments); + void captureContext(InstructionNode& instruction, Type& arguments); + InferContext* getMethodContext(); private: @@ -349,10 +378,11 @@ class TypeAnalyzer { }; private: - TypeSystem& m_system; - ControlGraph& m_graph; + TypeSystem& m_system; + ControlGraph& m_graph; + TContextStack& m_contextStack; InferContext& m_context; - Walker m_walker; + Walker m_walker; bool m_baseRun; bool m_literalBranch; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 45a7546..199eb76 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -4,24 +4,25 @@ using namespace type; static void printBlock(const Type& blockType, std::stringstream& stream) { - if (blockType.getSubTypes().size() < 2) { + if (blockType.getSubTypes().size() < Type::bstCaptureIndex) { stream << "(Block)"; return; } - TMethod* const method = blockType[0].getValue()->cast(); - const uint16_t offset = TInteger(blockType[1].getValue()); + TMethod* const method = blockType[Type::bstOrigin].getValue()->cast(); + const uint16_t offset = TInteger(blockType[Type::bstOffset].getValue()); - // Class>>method@offset + // Class>>method@offset#I[R][W]C stream << method->klass->name->toString() << ">>" << method->name->toString() - << "@" << offset - << blockType[3].toString() - << blockType[4].toString(); + << "@" << offset // block offset within a method + << "#" << blockType[Type::bstContextIndex].toString() // creating context index + << blockType[Type::bstReadsTemps].toString() // read temporaries indices + << blockType[Type::bstWritesTemps].toString(); // write temporaries indices - if (blockType.getSubTypes().size() > 5) - stream << blockType[5].toString(); + if (blockType.getSubTypes().size() > Type::bstCaptureIndex) + stream << blockType[Type::bstCaptureIndex].toString(); // capture context index } std::string Type::toString(bool subtypesOnly /*= false*/) const { @@ -146,15 +147,10 @@ Type& TypeAnalyzer::getArgumentType(const InstructionNode& instruction, std::siz void TypeAnalyzer::processInstruction(InstructionNode& instruction) { // std::printf("processing %.2u\n", instruction.getIndex()); - const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); - switch (instruction.getInstruction().getOpcode()) { - case opcode::pushArgument: - m_context[instruction] = m_context.getArgument(argument); - break; - case opcode::pushConstant: doPushConstant(instruction); break; case opcode::pushLiteral: doPushLiteral(instruction); break; + case opcode::pushArgument: doPushArgument(instruction); break; case opcode::pushTemporary: doPushTemporary(instruction); break; case opcode::assignTemporary: doAssignTemporary(instruction); break; @@ -204,6 +200,18 @@ void TypeAnalyzer::doPushLiteral(const InstructionNode& instruction) { m_context[instruction] = Type(literal); } +void TypeAnalyzer::doPushArgument(const InstructionNode& instruction) { + const TSmalltalkInstruction::TArgument index = instruction.getInstruction().getArgument(); + + if (m_blockType) { + if (InferContext* const methodContext = getMethodContext()) { + m_context[instruction] = methodContext->getArgument(index); + } + } else { + m_context[instruction] = m_context.getArgument(index); + } +} + void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { const Type& argType = m_context[*instruction.getArgument()]; const unaryBuiltIns::Opcode opcode = static_cast(instruction.getInstruction().getArgument()); @@ -240,7 +248,7 @@ void TypeAnalyzer::doSendUnary(const InstructionNode& instruction) { } -void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { +void TypeAnalyzer::doSendBinary(InstructionNode& instruction) { const Type& lhsType = m_context[*instruction.getArgument(0)]; const Type& rhsType = m_context[*instruction.getArgument(1)]; const binaryBuiltIns::Operator opcode = static_cast(instruction.getInstruction().getArgument()); @@ -306,7 +314,9 @@ void TypeAnalyzer::doSendBinary(const InstructionNode& instruction) { arguments.pushSubType(lhsType); arguments.pushSubType(rhsType); - if (InferContext* const context = m_system.inferMessage(selector, arguments)) + captureContext(instruction, arguments); + + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) result = context->getReturnType(); else result = Type(Type::tkPolytype); @@ -326,43 +336,73 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { result.set(globals.arrayClass, Type::tkArray); } +InferContext* TypeAnalyzer::getMethodContext() { + for (TContextStack* stack = m_contextStack.parent; stack; stack = stack->parent) { + const TInteger contextIndex((*m_blockType)[Type::bstContextIndex].getValue()); + + if (stack->context.getIndex() == static_cast(contextIndex)) + return &stack->context; + } + + return 0; +} + void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { if (const TauNode* const tau = instruction.getTauNode()) { const Type& tauType = m_context[*tau]; - //if (tauType.getKind() == Type::tkUndefined) if (tau->getKind() == TauNode::tkAggregator) processTau(*tau); m_context[instruction] = tauType; - } else if (m_blockType) { - // Block invocation primitive pass block arguments through creating method's temporaries. - // To simplify inference, we pass their types as context arguments. - const uint16_t argIndex = TInteger(m_blockType->getSubTypes()[2].getValue()); - const TSmalltalkInstruction::TArgument tempIndex = instruction.getInstruction().getArgument(); + } else if (m_blockType) { + const uint16_t argIndex = TInteger((*m_blockType)[Type::bstArgIndex].getValue()); + const uint16_t tempIndex = instruction.getInstruction().getArgument(); if (tempIndex >= argIndex) { m_context[instruction] = m_context.getArgument(tempIndex - argIndex); } else { - if (m_blockType->getSubTypes().size() > 5) { - const Type& readIndices = (*m_blockType)[3]; - const Type& contextTypes = (*m_blockType)[5]; - - for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { - if (TInteger(readIndices[i].getValue()) == tempIndex) { - m_context[instruction] = contextTypes[i]; - return; - } - } + if (InferContext* const methodContext = getMethodContext()) { + const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); + InferContext::TTypeMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + m_context[instruction] = closureTypes[tempIndex]; } - - m_context[instruction] = Type(Type::tkPolytype); } + } else { + // Method variables are initialized to nil by default + m_context[instruction] = Type(Type::tkPolytype); } } +void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { + const TauNode* const tau = instruction.getTauNode(); + assert(tau); + assert(tau->getKind() == TauNode::tkProvider); + + const ControlNode& argument = *instruction.getArgument(); + m_context[*tau] = m_context[argument]; + + if (!m_blockType) + return; + + InferContext* const methodContext = getMethodContext(); + if (!methodContext) + return; + + const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); + + InferContext::TTypeMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + const uint16_t tempIndex = instruction.getInstruction().getArgument(); + + InferContext::TTypeMap::iterator iType = closureTypes.find(tempIndex); + if (iType == closureTypes.end()) + closureTypes[tempIndex] = m_context[argument]; + else + closureTypes[tempIndex] &= m_context[argument]; +} + void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { if (const PushBlockNode* const pushBlock = instruction.cast()) { TMethod* const origin = pushBlock->getParsedBlock()->getContainer()->getOrigin(); @@ -372,16 +412,17 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { Type& blockType = m_context[instruction]; blockType.set(globals.blockClass, Type::tkMonotype); - blockType.pushSubType(origin); // [0] - blockType.pushSubType(Type(TInteger(offset))); // [1] - blockType.pushSubType(Type(TInteger(argIndex))); // [2] + blockType.pushSubType(origin); // [Type::bstOrigin] + blockType.pushSubType(Type(TInteger(offset))); // [Type::bstOffset] + blockType.pushSubType(Type(TInteger(argIndex))); // [Type::bstArgIndex] + blockType.pushSubType(Type(TInteger(m_context.getIndex()))); // [Type::bstContextIndex] // TODO Cache and reuse in TypeSystem::inferBlock() ControlGraph* const blockGraph = new ControlGraph(pushBlock->getParsedBlock()->getContainer(), pushBlock->getParsedBlock()); blockGraph->buildGraph(); typedef ControlGraph::TMetaInfo::TIndexList TIndexList; - const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; + const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; const TIndexList& writesTemporaries = blockGraph->getMeta().writesTemporaries; Type readIndices(globals.arrayClass, Type::tkArray); @@ -403,16 +444,8 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { writeIndices.pushSubType(Type(TInteger(writesTemporaries[index]))); } - blockType.pushSubType(readIndices); // [3] - blockType.pushSubType(writeIndices); // [4] - } -} - -void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { - if (const TauNode* const tau = instruction.getTauNode()) { - if (tau->getKind() == TauNode::tkProvider) { - m_context[*tau] = m_context[*instruction.getArgument()]; - } + blockType.pushSubType(readIndices); // [Type::bstReadsTemps] + blockType.pushSubType(writeIndices); // [Type::bstWritesTemps] } } @@ -421,6 +454,8 @@ class TypeLocator : public GraphWalker { TypeLocator(std::size_t tempIndex, const ControlGraph::TEdgeSet& backEdges, InferContext& context, bool noBackEdges) : tempIndex(tempIndex), backEdges(backEdges), context(context), noBackEdges(noBackEdges), firstResult(true) {} + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { if (InstructionNode* const instruction = node.cast()) { // TODO Also handle previously discovered non-trivial assign sites @@ -479,7 +514,7 @@ class TypeLocator : public GraphWalker { bool firstResult; }; -void TypeAnalyzer::processBlocks(InstructionNode& instruction, Type& arguments) { +void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) { TMethod* const currentMethod = m_graph.getParsedMethod()->getOrigin(); // We interested in literal blocks with context info from the same method @@ -489,24 +524,39 @@ void TypeAnalyzer::processBlocks(InstructionNode& instruction, Type& arguments) if (blockType.getValue() != globals.blockClass || blockType.getKind() != Type::tkMonotype) continue; // Not a block we may handle - if (blockType.getSubTypes().empty() || blockType[0].getValue() != currentMethod) + if (blockType.getSubTypes().empty() || blockType[Type::bstOrigin].getValue() != currentMethod) continue; // Non-literal or non-local block // Indexes of temporaries from the lexical context. See TypeAnalyzer::pushBlock() - const Type& readIndices = blockType[3]; - Type& temps = blockType.pushSubType(Type(globals.arrayClass, Type::tkArray)); + const Type& readIndices = blockType[Type::bstReadsTemps]; + // Index of the capture site + if (readIndices.getSubTypes().size()) + blockType.pushSubType(Type(TInteger(instruction.getIndex()))); // [Type::bstCaptureIndex] + + // Prepare captured context by writing inferred variable types at the capture point for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { // Detect types of temporaries accessible from the current call site + // TODO Move out of the loop + const std::size_t variableIndex = TInteger(readIndices[i].getValue()); + TypeLocator locator( - TInteger(readIndices[i].getValue()), // look for temporary with this index + variableIndex, // look for temporary with this index m_graph.getMeta().backEdges, m_context, - m_baseRun // base run = skip back edges + m_baseRun // base run = skip back edges ); locator.run(&instruction, st::GraphWalker::wdBackward); - temps.pushSubType(locator.result); + + InferContext::TTypeMap& typeMap = m_context.getBlockClosures()[instruction.getIndex()]; + InferContext::TTypeMap::iterator iType = typeMap.find(variableIndex); + + if (iType != typeMap.end()) + iType->second &= locator.result; + else + typeMap[variableIndex] = locator.result; + } } } @@ -518,10 +568,10 @@ void TypeAnalyzer::doSendMessage(InstructionNode& instruction) { TSymbol* const selector = literals[literalIndex]; Type& arguments = m_context[*instruction.getArgument()]; - processBlocks(instruction, arguments); + captureContext(instruction, arguments); Type& result = m_context[instruction]; - if (InferContext* const context = m_system.inferMessage(selector, arguments)) + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) result = context->getReturnType(); else result = Type(Type::tkPolytype); @@ -611,17 +661,17 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { } case primitive::blockInvoke: { - const Type& block = m_context[*instruction.getArgument(0)]; - const Type& arg = m_context[*instruction.getArgument(1)]; - + Type& block = m_context[*instruction.getArgument(0)]; Type arguments(Type::tkArray); - arguments.pushSubType(arg); + + if (instruction.getArgumentsCount() == 2) + arguments.pushSubType(m_context[*instruction.getArgument(1)]); if (instruction.getArgumentsCount() == 3) arguments.pushSubType(m_context[*instruction.getArgument(2)]); - if (BlockInferContext* invokeContext = m_system.inferBlock(block, arguments)) - primitiveResult = invokeContext->getReturnType(); + if (InferContext* const blockContext = m_system.inferBlock(block, arguments, &m_contextStack)) + primitiveResult = blockContext->getReturnType(); else primitiveResult = Type(Type::tkPolytype); @@ -781,7 +831,7 @@ ControlGraph* TypeSystem::getControlGraph(TMethod* method) { return controlGraph; } -InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments) { +InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments, TContextStack* parent) { if (!selector || arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) return 0; @@ -826,7 +876,7 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments if (! method) // TODO Redirect to #doesNotUnderstand: statically return 0; - InferContext* const inferContext = new InferContext(m_lastContextIndex++, arguments); + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); contextMap[arguments] = inferContext; ControlGraph* const methodGraph = getControlGraph(method); @@ -838,7 +888,8 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments selector->toString().c_str()); // TODO Handle recursive and tail calls - type::TypeAnalyzer analyzer(*this, *methodGraph, *inferContext); + TContextStack contextStack(*inferContext, parent); + type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); analyzer.run(); Type& returnType = inferContext->getReturnType(); @@ -852,15 +903,16 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments return inferContext; } -BlockInferContext* TypeSystem::inferBlock(const Type& block, const Type& arguments) { +InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContextStack* parent) { if (block.getKind() != Type::tkMonotype || arguments.getSubTypes().empty()) return 0; - // TODO Cache - BlockInferContext* const inferContext = new BlockInferContext(m_lastContextIndex++, arguments); - TMethod* const method = block[0].getValue()->cast(); - const uint16_t offset = TInteger(block[1].getValue()); + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); + + // TODO Cache + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); ControlGraph* const methodGraph = getControlGraph(method); assert(controlGraph); @@ -882,12 +934,12 @@ BlockInferContext* TypeSystem::inferBlock(const Type& block, const Type& argumen vis.run(); } - type::TypeAnalyzer analyzer(*this, *blockGraph, *inferContext); + TContextStack contextStack(*inferContext, parent); + type::TypeAnalyzer analyzer(*this, *blockGraph, contextStack); analyzer.run(&block); - std::printf("%s::%s -> %s, ^%s\n", arguments.toString().c_str(), block.toString().c_str(), - inferContext->getReturnType().toString().c_str(), - inferContext->getBlockReturnType().toString().c_str()); + std::printf("%s::%s -> %s\n", arguments.toString().c_str(), block.toString().c_str(), + inferContext->getReturnType().toString().c_str()); return inferContext; } From 5a8c69291786242f8d1fe3bda58e5e6bd589da9d Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 6 Jun 2016 23:09:07 +0600 Subject: [PATCH 046/105] Extracts TauLinker into separate file Issue: #17 --- CMakeLists.txt | 1 + include/analysis.h | 29 ++++ src/ControlGraph.cpp | 334 ------------------------------------------- src/TauLinker.cpp | 322 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 352 insertions(+), 334 deletions(-) create mode 100644 src/TauLinker.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d4679d..8757722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ add_library(stapi src/ParsedBlock.cpp src/ControlGraph.cpp + src/TauLinker.cpp src/ControlGraphVisualizer.cpp src/TypeAnalyzer.cpp ) diff --git a/include/analysis.h b/include/analysis.h index 74f3d57..53bd8cd 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -807,6 +807,35 @@ class BackEdgeDetector : public GraphWalker { TEdgeSet m_backEdges; }; +class TauLinker : private BackEdgeDetector { +public: + TauLinker(ControlGraph& graph) : m_graph(graph) {} + + void run() { BackEdgeDetector::run(m_graph); } + +private: + virtual st::GraphWalker::TVisitResult visitNode(st::ControlNode& node, const TPathNode* path); + virtual void nodesVisited(); + +private: + void optimizeTau(); + void eraseRedundantTau(); + void detectRedundantTau(); + + void createType(InstructionNode& instruction); + void processPushTemporary(InstructionNode& instruction); + +private: + ControlGraph& m_graph; + ControlGraph& getGraph() { return m_graph; } + + typedef std::set TInstructionSet; + TInstructionSet m_pendingNodes; + + typedef std::list TTauList; + TTauList m_providers; +}; + } // namespace st #endif diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 0ca55ed..5b3633c 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -735,340 +735,6 @@ class GraphOptimizer : public PlainNodeVisitor { TNodeSet m_nodesToRemove; }; -class TauLinker : private BackEdgeDetector { -public: - TauLinker(ControlGraph& graph) : m_graph(graph) {} - - void run() { BackEdgeDetector::run(m_graph); } - -private: - ControlGraph& m_graph; - ControlGraph& getGraph() { return m_graph; } - - typedef std::set TInstructionSet; - TInstructionSet m_pendingNodes; - - typedef std::list TTauList; - TTauList m_providers; - -private: - virtual st::GraphWalker::TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { - st::BackEdgeDetector::visitNode(node, path); - - if (InstructionNode* const instruction = node.cast()) { - switch (instruction->getInstruction().getOpcode()) { - case opcode::pushTemporary: - m_pendingNodes.insert(instruction); - break; - - case opcode::assignTemporary: - createType(*instruction); - break; - - default: - break; - } - } - - return st::GraphWalker::vrKeepWalking; - } - - virtual void nodesVisited() { - // Detected back edges - for (TEdgeSet::const_iterator iEdge = getBackEdges().begin(); iEdge != getBackEdges().end(); ++iEdge) { - if (traces_enabled) { - std::printf("Back edge: Node %.2u --> Node %.2u\n", - (*iEdge).from->getIndex(), - (*iEdge).to->getIndex() - ); - } - } - - getGraph().getMeta().hasLoops = !getBackEdges().empty(); - m_graph.getMeta().backEdges = getBackEdges(); - - // When all nodes visited, process the pending list - TInstructionSet::iterator iNode = m_pendingNodes.begin(); - for (; iNode != m_pendingNodes.end(); ++iNode) - processPushTemporary(**iNode); - - optimizeTau(); - } - -private: - typedef std::pair TTauPair; - typedef std::set TTauPairSet; - - typedef std::map TRedundantTauMap; - TRedundantTauMap m_redundantTaus; - - typedef std::set TTauSet; - TTauSet m_processedTaus; - - struct TAssignSite { - InstructionNode* instruction; - bool byBackEdge; - - TAssignSite(InstructionNode* instruction, bool byBackEdge) - : instruction(instruction), byBackEdge(byBackEdge) {} - }; - typedef std::vector TAssignSiteList; - -private: - void optimizeTau() { - detectRedundantTau(); - eraseRedundantTau(); - } - - void eraseRedundantTau() { - TRedundantTauMap::iterator iProvider = m_redundantTaus.begin(); - for (; iProvider != m_redundantTaus.end(); ++iProvider) { - if (traces_enabled) - printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); - - TTauPairSet& pendingTaus = iProvider->second; - TTauPairSet::iterator iPendingTau = pendingTaus.begin(); - - iPendingTau = pendingTaus.begin(); - for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { - TauNode* const remainingTau = iPendingTau->first; - TauNode* const redundantTau = iPendingTau->second; - - if (m_processedTaus.find(remainingTau) != m_processedTaus.end()) { - if (traces_enabled) - printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); - - continue; - } - - const TNodeSet& consumers = redundantTau->getConsumers(); - - // Remap all consumers to the remainingTau - TNodeSet::iterator iConsumer = consumers.begin(); - for ( ; iConsumer != consumers.end(); ++iConsumer) { - // FIXME Could there be non-instruction nodes? - if (InstructionNode* const instruction = (*iConsumer)->cast()) { - if (traces_enabled) { - printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", - instruction->getIndex(), - redundantTau->getIndex(), - remainingTau->getIndex()); - } - - instruction->setTauNode(remainingTau); - remainingTau->addConsumer(instruction); - } - } - - // Remove all incomings of the redundantTau - TauNode::TIncomingMap::const_iterator iIncoming = redundantTau->getIncomingMap().begin(); - for ( ; iIncoming != redundantTau->getIncomingMap().end(); ++iIncoming) { - - if (traces_enabled) { - printf("Redundant tau %.2u is no longer consumer of %.2u\n", - redundantTau->getIndex(), - iIncoming->first->getIndex()); - } - - iIncoming->first->removeConsumer(redundantTau); - } - - // Marking tau as processed - m_processedTaus.insert(redundantTau); - - if (traces_enabled) - printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); - } - } - - m_redundantTaus.clear(); - - // Erasing all redundant taus completely - TTauSet::const_iterator iProcessedTau = m_processedTaus.begin(); - for (; iProcessedTau != m_processedTaus.end(); ++iProcessedTau) { - TauNode* const processedTau = *iProcessedTau; - - if (traces_enabled) - printf("Erasing processed tau %.2u\n", processedTau->getIndex()); - - assert(processedTau->getIncomingMap().empty()); - getGraph().eraseNode(processedTau); - } - - m_processedTaus.clear(); - } - - void detectRedundantTau() { - TTauList::const_iterator iProvider = m_providers.begin(); - for (; iProvider != m_providers.end(); ++iProvider) { - const TNodeSet& consumers = (*iProvider)->getConsumers(); - if (consumers.size() < 2) - continue; - - if (traces_enabled) - printf("Looking for consumers of Tau %.2u (total %zu)\n", (*iProvider)->getIndex(), consumers.size()); - - TNodeSet::iterator iConsumer1 = consumers.begin(); - for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { - TauNode* const tau1 = (*iConsumer1)->cast(); - if (! tau1) - continue; - - TNodeSet::iterator iConsumer2 = iConsumer1; - ++iConsumer2; - - for (; iConsumer2 != consumers.end(); ++iConsumer2) { - TauNode* const tau2 = (*iConsumer2)->cast(); - if (!tau2) - continue; - - if (tau1->getIncomingMap() == tau2->getIncomingMap()) { - if (traces_enabled) - printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); - - m_redundantTaus[*iProvider].insert(std::make_pair(tau1, tau2)); - } - } - } - } - } - - void createType(InstructionNode& instruction) { - TauNode* const tau = getGraph().newNode(); - tau->setKind(TauNode::tkProvider); - tau->addIncoming(&instruction); - instruction.setTauNode(tau); - - m_providers.push_back(tau); - - if (traces_enabled) { - std::printf("New type: Node %u.%.2u --> Tau %.2u\n", - instruction.getDomain()->getBasicBlock()->getOffset(), - instruction.getIndex(), - tau->getIndex() - ); - } - } - - void processPushTemporary(InstructionNode& instruction) { - // Searching for all AssignTemporary's that provide a value for current node - AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges()); - locator.run(&instruction, GraphWalker::wdBackward); - - TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); - for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { - InstructionNode* const assignTemporary = (*iAssignSite).instruction->cast(); - assert(assignTemporary); - - TauNode* const assignType = assignTemporary->getTauNode(); - assert(assignType); - - if ((*iAssignSite).byBackEdge) - getGraph().getMeta().hasBackEdgeTau = true; - - if (! instruction.getTauNode()) { - // FIXME Could it be that the only incoming is accessible by back edge? - // Possbible scenario: push default nil, assigned later, branch up - - assignType->addConsumer(&instruction); - instruction.setTauNode(assignType); - - if (traces_enabled) { - std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", - assignType->getIndex(), - instruction.getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } - - } else { - TauNode* const current = instruction.getTauNode(); - - if (current->getKind() == TauNode::tkProvider) { - current->removeConsumer(&instruction); - - TauNode* const aggregator = getGraph().newNode(); - aggregator->setKind(TauNode::tkAggregator); - aggregator->addIncoming(current); - aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); - - aggregator->addConsumer(&instruction); - instruction.setTauNode(aggregator); - - if (traces_enabled) { - std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - aggregator->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } - - } else { - current->addIncoming(assignType, (*iAssignSite).byBackEdge); - - if (traces_enabled) { - std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } - } - } - } - } - - class AssignLocator : public GraphWalker { - public: - AssignLocator(TSmalltalkInstruction::TArgument argument, const TEdgeSet& backEdges) - : argument(argument), backEdges(backEdges) {} - - virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { - if (InstructionNode* const instruction = node.cast()) { - if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { - if (instruction->getInstruction().getArgument() == argument) { - bool hasBackEdge = false; - - // Searching for back edges in the located path - for (const TPathNode* p = path; p->prev; p = p->prev) { - const TEdgeSet::const_iterator iEdge = backEdges.find( - st::BackEdgeDetector::TEdge( - static_cast(p->node), - static_cast(p->prev->node) - ) - ); - - if (iEdge != backEdges.end()) { - hasBackEdge = true; - break; - } - } - - if (traces_enabled) - std::printf("Found assign site: Node %.2u, back edge: %s\n", - instruction->getIndex(), - hasBackEdge ? "yes" : "no"); - - assignSites.push_back(TAssignSite(instruction, hasBackEdge)); - return vrSkipPath; - } - } - } - - return vrKeepWalking; - } - - const TSmalltalkInstruction::TArgument argument; - const TEdgeSet& backEdges; - - TAssignSiteList assignSites; - }; -}; - void ControlGraph::buildGraph() { if (traces_enabled) diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp new file mode 100644 index 0000000..97edb8b --- /dev/null +++ b/src/TauLinker.cpp @@ -0,0 +1,322 @@ +#include +#include + +using namespace st; + +static const bool traces_enabled = false; + +struct TAssignSite { + InstructionNode* instruction; + bool byBackEdge; + + TAssignSite(InstructionNode* instruction, bool byBackEdge) + : instruction(instruction), byBackEdge(byBackEdge) {} +}; + +typedef std::vector TAssignSiteList; + +typedef std::pair TTauPair; +typedef std::set TTauPairSet; + +typedef std::map TRedundantTauMap; +TRedundantTauMap m_redundantTaus; + +typedef std::set TTauSet; +TTauSet m_processedTaus; + +class AssignLocator : public GraphWalker { +public: + AssignLocator(TSmalltalkInstruction::TArgument argument, const ControlGraph::TEdgeSet& backEdges) + : argument(argument), backEdges(backEdges) {} + + virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { + if (InstructionNode* const instruction = node.cast()) { + if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + if (instruction->getInstruction().getArgument() == argument) { + bool hasBackEdge = false; + + // Searching for back edges in the located path + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != backEdges.end()) { + hasBackEdge = true; + break; + } + } + + if (traces_enabled) { + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + hasBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, hasBackEdge)); + return vrSkipPath; + } + } + } + + return vrKeepWalking; + } + + const TSmalltalkInstruction::TArgument argument; + const ControlGraph::TEdgeSet& backEdges; + + TAssignSiteList assignSites; +}; + +st::GraphWalker::TVisitResult TauLinker::visitNode(st::ControlNode& node, const TPathNode* path) { + st::BackEdgeDetector::visitNode(node, path); + + if (InstructionNode* const instruction = node.cast()) { + switch (instruction->getInstruction().getOpcode()) { + case opcode::pushTemporary: + m_pendingNodes.insert(instruction); + break; + + case opcode::assignTemporary: + createType(*instruction); + break; + + default: + break; + } + } + + return st::GraphWalker::vrKeepWalking; +} + +void TauLinker::nodesVisited() { + // Detected back edges + for (TEdgeSet::const_iterator iEdge = getBackEdges().begin(); iEdge != getBackEdges().end(); ++iEdge) { + if (traces_enabled) { + std::printf("Back edge: Node %.2u --> Node %.2u\n", + (*iEdge).from->getIndex(), + (*iEdge).to->getIndex() + ); + } + } + + getGraph().getMeta().hasLoops = !getBackEdges().empty(); + m_graph.getMeta().backEdges = getBackEdges(); + + // When all nodes visited, process the pending list + TInstructionSet::iterator iNode = m_pendingNodes.begin(); + for (; iNode != m_pendingNodes.end(); ++iNode) + processPushTemporary(**iNode); + + optimizeTau(); +} + +void TauLinker::optimizeTau() { + detectRedundantTau(); + eraseRedundantTau(); +} + +void TauLinker::eraseRedundantTau() { + TRedundantTauMap::iterator iProvider = m_redundantTaus.begin(); + for (; iProvider != m_redundantTaus.end(); ++iProvider) { + if (traces_enabled) + printf("Now working on provider tau %.2u\n", (*iProvider).first->getIndex()); + + TTauPairSet& pendingTaus = iProvider->second; + TTauPairSet::iterator iPendingTau = pendingTaus.begin(); + + iPendingTau = pendingTaus.begin(); + for ( ; iPendingTau != pendingTaus.end(); ++iPendingTau) { + TauNode* const remainingTau = iPendingTau->first; + TauNode* const redundantTau = iPendingTau->second; + + if (m_processedTaus.find(remainingTau) != m_processedTaus.end()) { + if (traces_enabled) + printf("Tau %.2u was already processed earlier\n", remainingTau->getIndex()); + + continue; + } + + const TNodeSet& consumers = redundantTau->getConsumers(); + + // Remap all consumers to the remainingTau + TNodeSet::iterator iConsumer = consumers.begin(); + for ( ; iConsumer != consumers.end(); ++iConsumer) { + // FIXME Could there be non-instruction nodes? + if (InstructionNode* const instruction = (*iConsumer)->cast()) { + if (traces_enabled) { + printf("Remapping consumer %.2u from tau %.2u to remaining tau %.2u\n", + instruction->getIndex(), + redundantTau->getIndex(), + remainingTau->getIndex()); + } + + instruction->setTauNode(remainingTau); + remainingTau->addConsumer(instruction); + } + } + + // Remove all incomings of the redundantTau + TauNode::TIncomingMap::const_iterator iIncoming = redundantTau->getIncomingMap().begin(); + for ( ; iIncoming != redundantTau->getIncomingMap().end(); ++iIncoming) { + + if (traces_enabled) { + printf("Redundant tau %.2u is no longer consumer of %.2u\n", + redundantTau->getIndex(), + iIncoming->first->getIndex()); + } + + iIncoming->first->removeConsumer(redundantTau); + } + + // Marking tau as processed + m_processedTaus.insert(redundantTau); + + if (traces_enabled) + printf("Marking redundant tau %.2u as processed\n", redundantTau->getIndex()); + } + } + + m_redundantTaus.clear(); + + // Erasing all redundant taus completely + TTauSet::const_iterator iProcessedTau = m_processedTaus.begin(); + for (; iProcessedTau != m_processedTaus.end(); ++iProcessedTau) { + TauNode* const processedTau = *iProcessedTau; + + if (traces_enabled) + printf("Erasing processed tau %.2u\n", processedTau->getIndex()); + + assert(processedTau->getIncomingMap().empty()); + getGraph().eraseNode(processedTau); + } + + m_processedTaus.clear(); +} + +void TauLinker::detectRedundantTau() { + TTauList::const_iterator iProvider = m_providers.begin(); + for (; iProvider != m_providers.end(); ++iProvider) { + const TNodeSet& consumers = (*iProvider)->getConsumers(); + if (consumers.size() < 2) + continue; + + if (traces_enabled) + printf("Looking for consumers of Tau %.2u (total %zu)\n", (*iProvider)->getIndex(), consumers.size()); + + TNodeSet::iterator iConsumer1 = consumers.begin(); + for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { + TauNode* const tau1 = (*iConsumer1)->cast(); + if (! tau1) + continue; + + TNodeSet::iterator iConsumer2 = iConsumer1; + ++iConsumer2; + + for (; iConsumer2 != consumers.end(); ++iConsumer2) { + TauNode* const tau2 = (*iConsumer2)->cast(); + if (!tau2) + continue; + + if (tau1->getIncomingMap() == tau2->getIncomingMap()) { + if (traces_enabled) + printf("Tau %.2u and %.2u may be optimized\n", tau1->getIndex(), tau2->getIndex()); + + m_redundantTaus[*iProvider].insert(std::make_pair(tau1, tau2)); + } + } + } + } +} + +void TauLinker::createType(InstructionNode& instruction) { + TauNode* const tau = getGraph().newNode(); + tau->setKind(TauNode::tkProvider); + tau->addIncoming(&instruction); + instruction.setTauNode(tau); + + m_providers.push_back(tau); + + if (traces_enabled) { + std::printf("New type: Node %u.%.2u --> Tau %.2u\n", + instruction.getDomain()->getBasicBlock()->getOffset(), + instruction.getIndex(), + tau->getIndex() + ); + } +} + +void TauLinker::processPushTemporary(InstructionNode& instruction) { + // Searching for all AssignTemporary's that provide a value for current node + AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges()); + locator.run(&instruction, GraphWalker::wdBackward); + + TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); + for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { + InstructionNode* const assignTemporary = (*iAssignSite).instruction->cast(); + assert(assignTemporary); + + TauNode* const assignType = assignTemporary->getTauNode(); + assert(assignType); + + if ((*iAssignSite).byBackEdge) + getGraph().getMeta().hasBackEdgeTau = true; + + if (! instruction.getTauNode()) { + // FIXME Could it be that the only incoming is accessible by back edge? + // Possbible scenario: push default nil, assigned later, branch up + + assignType->addConsumer(&instruction); + instruction.setTauNode(assignType); + + if (traces_enabled) { + std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", + assignType->getIndex(), + instruction.getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } + + } else { + TauNode* const current = instruction.getTauNode(); + + if (current->getKind() == TauNode::tkProvider) { + current->removeConsumer(&instruction); + + TauNode* const aggregator = getGraph().newNode(); + aggregator->setKind(TauNode::tkAggregator); + aggregator->addIncoming(current); + aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); + + aggregator->addConsumer(&instruction); + instruction.setTauNode(aggregator); + + if (traces_enabled) { + std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + current->getIndex(), + aggregator->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } + + } else { + current->addIncoming(assignType, (*iAssignSite).byBackEdge); + + if (traces_enabled) { + std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + current->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } + } + } + } +} From c0f7a539e2ca32ae2e2a19caaa3c9775c87cc13e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 11 Jun 2016 21:29:21 +0600 Subject: [PATCH 047/105] Adds reverse iterator and eraseTauNodes() to the ControlGraph Issue: #17 --- include/analysis.h | 5 +++++ src/ControlGraph.cpp | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/analysis.h b/include/analysis.h index 53bd8cd..f6bd054 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -391,8 +391,11 @@ class ControlGraph { typedef std::list TNodeList; typedef TNodeList::iterator nodes_iterator; + typedef TNodeList::reverse_iterator reverse_iterator; nodes_iterator nodes_begin() { return m_nodes.begin(); } nodes_iterator nodes_end() { return m_nodes.end(); } + reverse_iterator nodes_rbegin() { return m_nodes.rbegin(); } + reverse_iterator nodes_rend() { return m_nodes.rend(); } bool isEmpty() const { return m_nodes.begin() == m_nodes.end(); } ControlNode* newNode(ControlNode::TNodeType type) { @@ -441,6 +444,8 @@ class ControlGraph { delete node; } + void eraseTauNodes(); + ~ControlGraph() { TDomainSet::iterator iDomain = m_domains.begin(); while (iDomain != m_domains.end()) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 5b3633c..b1117ba 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -776,3 +776,26 @@ void ControlGraph::buildGraph() linker.run(); } } + +void ControlGraph::eraseTauNodes() { + if (isEmpty()) + return; + + for (ControlGraph::reverse_iterator iNode = nodes_rbegin(); iNode != nodes_rend(); ) { + if (TauNode* const tau = (*iNode)->cast()) { + if (traces_enabled) + std::printf("Erasing tau %.2u\n", tau->getIndex()); + + const TNodeSet& consumers = tau->getConsumers(); + for (TNodeSet::iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { + if (InstructionNode* const instruction = (*iNode)->cast()) { + assert(instruction->getTauNode() == tau); + instruction->setTauNode(0); + } + } + + ++iNode; + eraseNode(tau); + } + } +} \ No newline at end of file From bc2eb8e957ac68af90631dc7cff452ff7a03e32b Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 11 Jun 2016 21:32:04 +0600 Subject: [PATCH 048/105] Renames TypeAnalyzer::getTypeList() to getTypes() Issue: #17 --- include/inference.h | 4 ++-- src/TypeAnalyzer.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/inference.h b/include/inference.h index c903929..4345b5d 100644 --- a/include/inference.h +++ b/include/inference.h @@ -251,7 +251,7 @@ class InferContext { } const Type& getArguments() const { return m_arguments; } - const TTypeList& getTypeList() const { return m_instructions; } + const TTypeList& getTypes() const { return m_types; } Type& getReturnType() { return m_returnType; } @@ -271,7 +271,7 @@ class InferContext { TMethod* const m_method; const std::size_t m_index; const Type m_arguments; - TTypeList m_instructions; + TTypeList m_types; Type m_returnType; TBlockClosures m_blockClosures; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 199eb76..370e518 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -93,8 +93,8 @@ void TypeAnalyzer::run(const Type* blockType /*= 0*/) { m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); std::cout << "Base run:" << std::endl; - type::TTypeList::const_iterator iType = m_context.getTypeList().begin(); - for (; iType != m_context.getTypeList().end(); ++iType) + type::TTypeList::const_iterator iType = m_context.getTypes().begin(); + for (; iType != m_context.getTypes().end(); ++iType) std::cout << iType->first << " " << iType->second.toString() << std::endl; // If single return is detected, replace composite with it's subtype @@ -121,8 +121,8 @@ void TypeAnalyzer::run(const Type* blockType /*= 0*/) { m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); std::cout << "Induction run:" << std::endl; - type::TTypeList::const_iterator iType = m_context.getTypeList().begin(); - for (; iType != m_context.getTypeList().end(); ++iType) + type::TTypeList::const_iterator iType = m_context.getTypes().begin(); + for (; iType != m_context.getTypes().end(); ++iType) std::cout << iType->first << " " << iType->second.toString() << std::endl; if (returnType.getSubTypes().size() == 1) From d2e3ada9fbb52cb30c4f12d1ce3b7ea15164a176 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 18 Jun 2016 18:04:44 +0600 Subject: [PATCH 049/105] Adds ClosureNode and it's handling in TauLinker Issue: #17 --- include/analysis.h | 46 +++++++- src/ControlGraph.cpp | 23 ++++ src/TauLinker.cpp | 272 +++++++++++++++++++++++++++++++------------ 3 files changed, 267 insertions(+), 74 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index f6bd054..3a1d07f 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -279,9 +279,10 @@ class TauNode : public ControlNode { const TIncomingMap& getIncomingMap() const { return m_incomingMap; } enum TKind { - tkUnknown, + tkUnknown = 0, tkProvider, - tkAggregator + tkAggregator, + tkClosure }; void setKind(TKind value) { m_kind = value; } @@ -292,6 +293,20 @@ class TauNode : public ControlNode { TKind m_kind; }; +class ClosureTauNode : public TauNode { +public: + ClosureTauNode(uint32_t index) : TauNode(index), m_origin(0) { } + + typedef std::size_t TIndex; + typedef std::vector TIndexList; + + InstructionNode* getOrigin() const { return m_origin; } + void setOrigin(InstructionNode* node) { m_origin = node; } + +private: + InstructionNode* m_origin; +}; + // Domain is a group of nodes within a graph // that represent a single basic block class ControlDomain { @@ -560,6 +575,10 @@ template<> BranchNode* ControlNode::cast(); template<> const BranchNode* ControlNode::cast() const; template<> BranchNode* ControlGraph::newNode(); +template<> ClosureTauNode* ControlNode::cast(); +template<> ClosureTauNode* ControlGraph::newNode(); +template<> const ClosureTauNode* ControlNode::cast() const; + class DomainVisitor { public: DomainVisitor(ControlGraph* graph) : m_graph(graph) { } @@ -818,6 +837,26 @@ class TauLinker : private BackEdgeDetector { void run() { BackEdgeDetector::run(m_graph); } + void addClosureNode( + const InstructionNode& node, + const ClosureTauNode::TIndexList& readIndices, + const ClosureTauNode::TIndexList& writeIndices); + + struct TClosureInfo { + ClosureTauNode::TIndexList readIndices; + ClosureTauNode::TIndexList writeIndices; + + bool writesIndex(ClosureTauNode::TIndex index) const { + return std::find(writeIndices.begin(), writeIndices.end(), index) != writeIndices.end(); + } + }; + + typedef std::map TClosureMap; + const TClosureMap& getClosures() const { return m_closures; } + + void eraseTauNodes(); + void reset(); + private: virtual st::GraphWalker::TVisitResult visitNode(st::ControlNode& node, const TPathNode* path); virtual void nodesVisited(); @@ -829,6 +868,7 @@ class TauLinker : private BackEdgeDetector { void createType(InstructionNode& instruction); void processPushTemporary(InstructionNode& instruction); + void processClosure(InstructionNode& instruction); private: ControlGraph& m_graph; @@ -839,6 +879,8 @@ class TauLinker : private BackEdgeDetector { typedef std::list TTauList; TTauList m_providers; + + TClosureMap m_closures; }; } // namespace st diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index b1117ba..da403c3 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -58,6 +58,27 @@ template<> PushBlockNode* ControlGraph::newNode() { return static_cast(node); } +template<> ClosureTauNode* ControlGraph::newNode() { + ClosureTauNode* const node = new ClosureTauNode(m_lastNodeIndex++); + m_nodes.push_back(node); + return static_cast(node); +} + +template<> ClosureTauNode* ControlNode::cast() { + if (this->getNodeType() != ntTau) + return 0; + + TauNode* const node = static_cast(this); + if (node->getKind() != TauNode::tkClosure) + return 0; + + return static_cast(this); +} + +template<> const ClosureTauNode* ControlNode::cast() const { + return const_cast(this)->cast(); +} + template<> BranchNode* ControlNode::cast() { if (this->getNodeType() != ntInstruction) return 0; @@ -796,6 +817,8 @@ void ControlGraph::eraseTauNodes() { ++iNode; eraseNode(tau); + } else { + ++iNode; } } } \ No newline at end of file diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp index 97edb8b..17ce7bc 100644 --- a/src/TauLinker.cpp +++ b/src/TauLinker.cpp @@ -26,51 +26,116 @@ TTauSet m_processedTaus; class AssignLocator : public GraphWalker { public: - AssignLocator(TSmalltalkInstruction::TArgument argument, const ControlGraph::TEdgeSet& backEdges) - : argument(argument), backEdges(backEdges) {} + AssignLocator( + TSmalltalkInstruction::TArgument argument, + const ControlGraph::TEdgeSet& backEdges, + const TauLinker::TClosureMap& closures + ) : argument(argument), backEdges(backEdges), closures(closures) {} virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { - if (InstructionNode* const instruction = node.cast()) { - if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + // TODO Phi node + + InstructionNode* const instruction = node.cast(); + if (!instruction) + return vrKeepWalking; + + //assert(instruction); + + switch (instruction->getInstruction().getOpcode()) { + case opcode::assignTemporary: if (instruction->getInstruction().getArgument() == argument) { - bool hasBackEdge = false; - - // Searching for back edges in the located path - for (const TPathNode* p = path; p->prev; p = p->prev) { - const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( - st::BackEdgeDetector::TEdge( - static_cast(p->node), - static_cast(p->prev->node) - ) - ); - - if (iEdge != backEdges.end()) { - hasBackEdge = true; - break; - } - } + const bool viaBackEdge = containsBackEdge(path); if (traces_enabled) { std::printf("Found assign site: Node %.2u, back edge: %s\n", instruction->getIndex(), - hasBackEdge ? "yes" : "no"); + viaBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); + return vrSkipPath; + } + break; + + case opcode::sendBinary: + case opcode::sendMessage: { + TauLinker::TClosureMap::const_iterator iClosure = closures.find(instruction); + if (iClosure == closures.end()) + break; + + if (iClosure->second.writesIndex(argument)) { + const bool viaBackEdge = containsBackEdge(path); + + if (traces_enabled) { + std::printf("Found assigning closure: Node %.2u, back edge: %s\n", + instruction->getIndex(), + viaBackEdge ? "yes" : "no"); } - assignSites.push_back(TAssignSite(instruction, hasBackEdge)); + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); return vrSkipPath; } + + break; } + + default: + break; } + if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { + if (instruction->getInstruction().getArgument() == argument) { + const bool viaBackEdge = containsBackEdge(path); + + if (traces_enabled) { + std::printf("Found assign site: Node %.2u, back edge: %s\n", + instruction->getIndex(), + viaBackEdge ? "yes" : "no"); + } + + assignSites.push_back(TAssignSite(instruction, viaBackEdge)); + return vrSkipPath; + } + } + return vrKeepWalking; } + bool containsBackEdge(const TPathNode* path) const { + // Search for back edges in the located path + + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != backEdges.end()) + return true; + } + + return false; + } + const TSmalltalkInstruction::TArgument argument; const ControlGraph::TEdgeSet& backEdges; + const TauLinker::TClosureMap& closures; TAssignSiteList assignSites; }; +void TauLinker::addClosureNode( + const st::InstructionNode& node, + const ClosureTauNode::TIndexList& readIndices, + const ClosureTauNode::TIndexList& writeIndices) +{ + TClosureInfo& closure = m_closures[&node]; + closure.readIndices = readIndices; + closure.writeIndices = writeIndices; +} + st::GraphWalker::TVisitResult TauLinker::visitNode(st::ControlNode& node, const TPathNode* path) { st::BackEdgeDetector::visitNode(node, path); @@ -84,6 +149,11 @@ st::GraphWalker::TVisitResult TauLinker::visitNode(st::ControlNode& node, const createType(*instruction); break; + case opcode::sendBinary: + case opcode::sendMessage: + m_pendingNodes.insert(instruction); + break; + default: break; } @@ -108,8 +178,23 @@ void TauLinker::nodesVisited() { // When all nodes visited, process the pending list TInstructionSet::iterator iNode = m_pendingNodes.begin(); - for (; iNode != m_pendingNodes.end(); ++iNode) - processPushTemporary(**iNode); + for (; iNode != m_pendingNodes.end(); ++iNode) { + InstructionNode& node = **iNode; + + switch (node.getInstruction().getOpcode()) { + case opcode::pushTemporary: + processPushTemporary(node); + break; + + case opcode::sendBinary: + case opcode::sendMessage: + processClosure(node); + break; + + default: + break; + } + } optimizeTau(); } @@ -210,7 +295,7 @@ void TauLinker::detectRedundantTau() { TNodeSet::iterator iConsumer1 = consumers.begin(); for ( ; iConsumer1 != consumers.end(); ++iConsumer1) { TauNode* const tau1 = (*iConsumer1)->cast(); - if (! tau1) + if (! tau1 || tau1->getKind() == TauNode::tkClosure) continue; TNodeSet::iterator iConsumer2 = iConsumer1; @@ -218,7 +303,7 @@ void TauLinker::detectRedundantTau() { for (; iConsumer2 != consumers.end(); ++iConsumer2) { TauNode* const tau2 = (*iConsumer2)->cast(); - if (!tau2) + if (!tau2 || tau2->getKind() == TauNode::tkClosure) continue; if (tau1->getIncomingMap() == tau2->getIncomingMap()) { @@ -241,18 +326,28 @@ void TauLinker::createType(InstructionNode& instruction) { m_providers.push_back(tau); if (traces_enabled) { - std::printf("New type: Node %u.%.2u --> Tau %.2u\n", + std::printf("New type: Node %u.%.2u --> Tau %.2u, type %d\n", instruction.getDomain()->getBasicBlock()->getOffset(), instruction.getIndex(), - tau->getIndex() + tau->getIndex(), + tau->getKind() + ); } } void TauLinker::processPushTemporary(InstructionNode& instruction) { - // Searching for all AssignTemporary's that provide a value for current node - AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges()); - locator.run(&instruction, GraphWalker::wdBackward); + // Searching for all AssignTemporary's and closures that provide a value for current node + AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges(), getClosures()); + locator.run(&instruction, GraphWalker::wdBackward, false); + + TauNode* aggregator = 0; + if (locator.assignSites.size() > 1) { + aggregator = m_graph.newNode(); + aggregator->setKind(TauNode::tkAggregator); + aggregator->addConsumer(&instruction); + instruction.setTauNode(aggregator); + } TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { @@ -265,58 +360,91 @@ void TauLinker::processPushTemporary(InstructionNode& instruction) { if ((*iAssignSite).byBackEdge) getGraph().getMeta().hasBackEdgeTau = true; - if (! instruction.getTauNode()) { - // FIXME Could it be that the only incoming is accessible by back edge? - // Possbible scenario: push default nil, assigned later, branch up - + if (aggregator) { + aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); + } else { assignType->addConsumer(&instruction); instruction.setTauNode(assignType); + } - if (traces_enabled) { - std::printf("Inherit type: Tau %.2u <-- %.2u, assign site %.2u is %s\n", - assignType->getIndex(), - instruction.getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } + if (traces_enabled) { + std::printf("Tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", + instruction.getIndex(), + aggregator ? aggregator->getIndex() : assignType->getIndex(), + assignTemporary->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); + } - } else { - TauNode* const current = instruction.getTauNode(); + } +} - if (current->getKind() == TauNode::tkProvider) { - current->removeConsumer(&instruction); +void TauLinker::processClosure(InstructionNode& instruction) { + if (traces_enabled) + std::printf("Analyzing closure %.2u\n", instruction.getIndex()); - TauNode* const aggregator = getGraph().newNode(); - aggregator->setKind(TauNode::tkAggregator); - aggregator->addIncoming(current); - aggregator->addIncoming(assignType, (*iAssignSite).byBackEdge); + TClosureMap::const_iterator iClosure = m_closures.find(&instruction); + if (iClosure == m_closures.end()) + return; - aggregator->addConsumer(&instruction); - instruction.setTauNode(aggregator); + const TClosureInfo& closure = iClosure->second; - if (traces_enabled) { - std::printf("Remapped tau: Node %.2u --> Tau %.2u to Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - aggregator->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } + if (!closure.readIndices.size() && !closure.writeIndices.size()) + return; - } else { - current->addIncoming(assignType, (*iAssignSite).byBackEdge); + ClosureTauNode* const closureTau = m_graph.newNode(); + closureTau->setOrigin(&instruction); + closureTau->setKind(TauNode::tkClosure); + closureTau->addConsumer(&instruction); + instruction.setTauNode(closureTau); - if (traces_enabled) { - std::printf("Attached to existing tau: Node %.2u --> Tau %.2u, assign site %.2u is %s\n", - instruction.getIndex(), - current->getIndex(), - assignTemporary->getIndex(), - (*iAssignSite).byBackEdge ? "below" : "above" - ); - } + m_providers.push_back(closureTau); + + for (ClosureTauNode::TIndex index = 0; index < closure.readIndices.size(); index++) { + // Searching for all AssignTemporary's that provide a value for current node + AssignLocator locator(closure.readIndices[index], getBackEdges(), getClosures()); + locator.run(&instruction, GraphWalker::wdBackward, false); + + TauNode* aggregator = 0; + if (locator.assignSites.size() > 1) { + aggregator = m_graph.newNode(); + aggregator->setKind(TauNode::tkAggregator); + closureTau->addIncoming(aggregator); + } + + TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); + for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { + InstructionNode* const assignNode = (*iAssignSite).instruction->cast(); + assert(assignTemporary); + + TauNode* const assignTau = assignNode->getTauNode(); + assert(assignType); + + if ((*iAssignSite).byBackEdge) + getGraph().getMeta().hasBackEdgeTau = true; + + if (aggregator) + aggregator->addIncoming(assignTau, (*iAssignSite).byBackEdge); + else + closureTau->addIncoming(assignTau); + + if (traces_enabled) { + std::printf("Tau %.2u <-- %s %.2u, assign site %.2u is %s\n", + assignTau->getIndex(), + aggregator ? "aggregator" : "closure", + aggregator ? aggregator->getIndex() : closureTau->getIndex(), + assignNode->getIndex(), + (*iAssignSite).byBackEdge ? "below" : "above" + ); } } } } + +void TauLinker::reset() { + m_graph.eraseTauNodes(); + m_providers.clear(); + m_pendingNodes.clear(); + m_closures.clear(); + resetStopNodes(); +} From 8536cb3a0ce5d0b59726c60b744f5728c295799c Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 18 Jun 2016 18:07:25 +0600 Subject: [PATCH 050/105] Adds inference of block closures and caught temporaries Issue: #17 --- doc/types.txt | 87 +++++++++ include/analysis.h | 11 +- include/inference.h | 29 ++- src/ControlGraph.cpp | 15 +- src/TypeAnalyzer.cpp | 433 ++++++++++++++++++++++++++++++++++--------- 5 files changed, 466 insertions(+), 109 deletions(-) diff --git a/doc/types.txt b/doc/types.txt index 4b595ee..976fac9 100644 --- a/doc/types.txt +++ b/doc/types.txt @@ -186,3 +186,90 @@ Впрочем, ветви кэша кодируются как вызовы специализированных версий методов, поскольку в пределах ветви класс отправителя (self) становится известен. + +Блоки, акт второй. + +Классификация блоков относительно типов + ContextFree [ 42 ], [ :x | x + 1 ], [ :x :y | x < y ] + ContextAccessor [ x message ], [ x + 1 ], [ anArgument message: aTemporary ] + ContextMutator [ x <- nil ], [ :x | sum <- sum + x ] + + BlockReturn [ ^42 ] + +Классификация точки вызова блоков + Never + Once y <- [ :x | x ] value: 42 + Maybe + Multi 1 to: 10 do: [ :x | sum <- sum + x ] + Escape *::anObject take: (Block)::aBlock + + +В зависимости от вида блока и точки его вызова, вывод типов может быть реализован по-разному. + +В наиболее общем случае необходимо выполнять подстановку графа блока в лексический контекст +вызывающего метода в зависимости от того как происходит работа с блоком в теле обработчика сообщения. + +Never + Блок не учитывается в выводе типов + +Once + Блок подключается параллельно инструкции, принимающей его параметром, например 1 to: 10: do: [ ... ] + +Maybe + То же, что и для Once, но есть параллельное ребро мимо блока + +Multi + То же, что и Multi, но еще добавляется возвратное ребро в начало блока + +Escape + Вызовы этого типа не могут быть представлены в виде графа и блокируют вывод типов для задействованных переменных + +После построения inline графа, выполняется проход TauLinker-а на нем, +полученные тау узлы могут быть использованы для вывода типов переменных. + + +Итак, алгоритм. + +Для метода, в лексическом контексте которого объявляется блок: + 1. Получаем список переменных, которые мутирует блок (известно после анализа графа блока) + 2. Выполняем анализ для каждой посылки, которая использует блок как параметр + 3. В точку вызова передаем тип всех временных переменных, используемых блоком (как аргументы) + 4. После анализа посылки изучаем контекст на предмет поменявшихся типов переменных + 5. Если тип поменялся, то маркируем текущую посылку как мутатор + 6. После маркировки всех мутаторов обновляем тау узлы графа и точки их использования + 7. Пересчитываем все заново с обновленным графом (новых мутаторов добавиться не должно) + +Для метода, принимающего литеральный блок параметром: + 1. В каждой посылке, использующей переданный аргументом блок выполняем анализ «как есть» + 2. Аккумулируем типы меняющихся переменных + 3. Возвращаем аккумулированный результат + +Для методов Block>>value* (код обработчика примитива 8) + 1. Проводим анализ блока, передав типы аргументов и переменных + 2. Получаем типы переменных и результата + 3. Возвращаем все добро наверх + + +Проходы анализатора, в зависимости от типа метода: + 1. Метод, не содержащий ветвлений доказывается за 1 проход + 2. Метод, с литеральными условиями при ветвлениях доказывается за 1 проход + 3. Метод, не содержащий циклов (обратных ребер) доказывается за 1 проход + 4. Метод, содержащий циклы, но не имеющий тау узлов с обратными ребрами, доказывается за 1? проход + 5. Метод, содержащий циклы и имеющий циклическую зависимость по данным (x <- x + 1), доказывается за 2 прохода + + 6. Метод, содержащий литеральные блоки, вне циклов: если нет замыканий или только читают — 1 проход, если пишут — минимум 2 прохода + 7. Метод, содержащий литеральные блоки, в циклах — минимум 3 прохода + 8. Метод, содержащий литеральные блоки, записанные в переменную + + Блоки, вызываемые несколько раз (утекающие в поле) не могут быть выведены, без отслеживания полей + + 9. Метод, содержащий литеральные блоки, утекающие в неизвестность — полная блокировка вывода переменных, которые они пишут + + +Алгоритм анализатора: + 1. Делаем базовый проход без обратных ребер + 2. Если предыдущий проход вывел возврат по литеральному пути и метод не содержит блоков мутаторов — выход + 3. Делаем базовый проход с нетривиальными мутаторами + 4. Если есть литеральный возврат — выход + 5. Делаем индукционный переход + diff --git a/include/analysis.h b/include/analysis.h index 3a1d07f..e461043 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -264,12 +264,12 @@ class PhiNode : public ControlNode { // Tau node is reserved for further use in type inference subsystem. // It will link variable type transitions across a method. class TauNode : public ControlNode { - public: TauNode(uint32_t index) : ControlNode(index), m_kind(tkUnknown) { } virtual TNodeType getNodeType() const { return ntTau; } - typedef std::map TIncomingMap; + typedef std::map TIncomingMap; + typedef std::map TIncomingIndexMap; void addIncoming(ControlNode* node, bool byBackEdge = false) { m_incomingMap[node] = byBackEdge; @@ -516,6 +516,7 @@ class ControlGraph { bool isBlock; bool hasBlockReturn; bool hasLiteralBlocks; + bool hasMutatingBlocks; bool hasLoops; bool hasBackEdgeTau; @@ -697,13 +698,13 @@ class GraphWalker { wdBackward }; - void run(ControlNode* startNode, TWalkDirection direction) { + void run(ControlNode* startNode, TWalkDirection direction, bool visitStart = true) { assert(startNode); m_direction = direction; TPathNode path(startNode); - if (visitNode(*startNode, &path) != vrKeepWalking) + if (visitStart && visitNode(*startNode, &path) != vrKeepWalking) return; walkIn(startNode, &path); @@ -858,7 +859,7 @@ class TauLinker : private BackEdgeDetector { void reset(); private: - virtual st::GraphWalker::TVisitResult visitNode(st::ControlNode& node, const TPathNode* path); + virtual GraphWalker::TVisitResult visitNode(ControlNode& node, const TPathNode* path); virtual void nodesVisited(); private: diff --git a/include/inference.h b/include/inference.h index 4345b5d..21d432f 100644 --- a/include/inference.h +++ b/include/inference.h @@ -231,7 +231,7 @@ class Type { }; typedef std::size_t TNodeIndex; -typedef std::map TTypeList; +typedef std::map TTypeMap; class InferContext { public: @@ -251,27 +251,31 @@ class InferContext { } const Type& getArguments() const { return m_arguments; } - const TTypeList& getTypes() const { return m_types; } + const TTypeMap& getTypes() const { return m_types; } + void resetTypes() { m_types.clear(); } Type& getReturnType() { return m_returnType; } - Type& getInstructionType(TNodeIndex index) { return m_instructions[index]; } - Type& operator[] (TNodeIndex index) { return m_instructions[index]; } + Type& getInstructionType(TNodeIndex index) { return m_types[index]; } + Type& operator[] (TNodeIndex index) { return m_types[index]; } Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } // variable index -> aggregated type - typedef std::map TTypeMap; + typedef std::size_t TVariableIndex; + typedef std::map TVariableMap; // capture site index -> captured context types - typedef std::map TBlockClosures; + typedef std::size_t TSiteIndex; + typedef std::map TBlockClosures; TBlockClosures& getBlockClosures() { return m_blockClosures; } + void resetClosures() { m_blockClosures.clear(); } private: TMethod* const m_method; const std::size_t m_index; const Type m_arguments; - TTypeList m_types; + TTypeMap m_types; Type m_returnType; TBlockClosures m_blockClosures; @@ -319,6 +323,7 @@ class TypeAnalyzer { m_graph(graph), m_contextStack(contextStack), m_context(contextStack.context), + m_tauLinker(m_graph), m_walker(*this) { } @@ -326,6 +331,9 @@ class TypeAnalyzer { void run(const Type* blockType = 0); private: + std::string getMethodName(); + bool basicRun(); + void processInstruction(InstructionNode& instruction); void processTau(const TauNode& tau); @@ -355,6 +363,8 @@ class TypeAnalyzer { void captureContext(InstructionNode& instruction, Type& arguments); InferContext* getMethodContext(); + void fillLinkerClosures(); + private: class Walker : public GraphWalker { @@ -382,8 +392,13 @@ class TypeAnalyzer { ControlGraph& m_graph; TContextStack& m_contextStack; InferContext& m_context; + + TauLinker m_tauLinker; Walker m_walker; + typedef std::map TSiteMap; + TSiteMap m_siteMap; + bool m_baseRun; bool m_literalBranch; diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index da403c3..a962890 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -9,6 +9,7 @@ ControlGraph::TMetaInfo::TMetaInfo() : isBlock(false), hasBlockReturn(false), hasLiteralBlocks(false), + hasMutatingBlocks(false), hasLoops(false), hasBackEdgeTau(false), usesSelf(false), @@ -202,7 +203,13 @@ void GraphConstructor::processNode(InstructionNode* node) break; case opcode::pushBlock: { - m_graph->getMeta().hasLiteralBlocks = true; + // FIXME m_graph->getMeta().hasLiteralBlocks = true; + // + // NOTE Technically, we may set the meta here, + // but we may get false-positive value + // due to a known bug in the imageBuilder + // which leaves dead PushBlock instructions + // when inlining key selectors. const uint16_t blockEndOffset = node->getInstruction().getExtra(); ParsedMethod* const parsedMethod = m_graph->getParsedMethod(); @@ -790,12 +797,6 @@ void ControlGraph::buildGraph() if (traces_enabled) std::printf("Phase 4. Linking PushTemporary and AssignTemporary nodes\n"); - - // Linking PushTemporary and AssignTemporary pairs - { - TauLinker linker(*this); - linker.run(); - } } void ControlGraph::eraseTauNodes() { diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 370e518..59f9fd5 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -80,10 +80,150 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { return stream.str(); } +void TypeAnalyzer::fillLinkerClosures() { + typedef InferContext::TBlockClosures::const_iterator TClosureIterator; + InferContext::TBlockClosures& closures = m_context.getBlockClosures(); + + for (TClosureIterator iClosure = closures.begin(); iClosure != closures.end(); ++iClosure) { + const InferContext::TSiteIndex siteIndex = iClosure->first; + InstructionNode* const instruction = m_siteMap[siteIndex]; + + if (!instruction) { + std::cout << "site index " << siteIndex << " is not found" << std::endl; + continue; + } + + Type& arguments = m_context[*instruction->getArgument()]; + + std::cout + << "analyzing potential closure args for " << instruction->getIndex() << " :: " + << arguments.toString() << std::endl; + + for (std::size_t argIndex = 0; argIndex < arguments.getSubTypes().size(); argIndex++) { + Type& blockType = arguments[argIndex]; + +// std::cout +// << "analyzing potential closure arg " << argIndex << " :: " +// << blockType.toString() << std::endl; + + if (blockType.getValue() != globals.blockClass || blockType.getKind() != Type::tkMonotype) + continue; // Not a block we may handle + + if (blockType.getSubTypes().empty()) + continue; // Non-literal or non-local block + + const Type& blockReadIndices = blockType[Type::bstReadsTemps]; + const Type& blockWrtieIndices = blockType[Type::bstWritesTemps]; + + ClosureTauNode::TIndexList readIndices; + ClosureTauNode::TIndexList wrtieIndices; + + readIndices.reserve(blockReadIndices.getSubTypes().size()); + wrtieIndices.reserve(blockWrtieIndices.getSubTypes().size()); + + for (std::size_t i = 0; i < blockReadIndices.getSubTypes().size(); i++) { + const std::size_t variableIndex = TInteger(blockReadIndices[i].getValue()); + readIndices.push_back(variableIndex); + } + + for (std::size_t i = 0; i < blockWrtieIndices.getSubTypes().size(); i++) { + const std::size_t variableIndex = TInteger(blockWrtieIndices[i].getValue()); + wrtieIndices.push_back(variableIndex); + } + + std::cout + << "adding closure node " << siteIndex + << " reads " << blockReadIndices.toString() + << " writes " << blockWrtieIndices.toString() + << std::endl; + + m_tauLinker.addClosureNode(*instruction, readIndices, wrtieIndices); + } + } +} + +bool TypeAnalyzer::basicRun() { + m_walker.resetStopNodes(); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + + Type& returnType = m_context.getReturnType(); + const bool singleReturn = (returnType.getSubTypes().size() == 1); + + if (singleReturn) + returnType = returnType[0]; + else + returnType.setKind(Type::tkComposite); + + return singleReturn; +} + +std::string TypeAnalyzer::getMethodName() { + TMethod* const method = m_graph.getParsedMethod()->getOrigin(); + std::ostringstream ss; + ss << method->klass->name->toString() + ">>" + method->name->toString(); + if (m_blockType) + ss << "@" << TInteger((*m_blockType)[Type::bstOffset].getValue()).getValue(); + + return ss.str(); +} + + void TypeAnalyzer::run(const Type* blockType /*= 0*/) { if (m_graph.isEmpty()) return; + m_blockType = blockType; + const std::string methodName = getMethodName(); + std::cout << "Initial pass on " << methodName << std::endl; + + m_baseRun = true; + m_literalBranch = true; + m_tauLinker.run(); + bool singleReturn = basicRun(); + + if (m_literalBranch && singleReturn && !m_graph.getMeta().hasMutatingBlocks) + return; + + TTypeMap controlTypes; + +// for (std::size_t passNumber = 0; ; passNumber++) + std::size_t passNumber = 0; + { + if (m_graph.getMeta().hasLiteralBlocks) { + m_tauLinker.reset(); + fillLinkerClosures(); + m_tauLinker.run(); + m_context.resetTypes(); + + std::cout << "Base pass on " << methodName << " (" << passNumber << ")" << std::endl; + m_baseRun = true; + m_literalBranch = true; + singleReturn = basicRun(); + + if (m_literalBranch && singleReturn && !m_graph.getMeta().hasMutatingBlocks) + return; + } + + { + ControlGraphVisualizer vis(&m_graph, methodName, "dots/"); + vis.run(); + } + + std::cout << "Induction pass on " << methodName << " (" << passNumber << ")" << std::endl; + m_baseRun = false; + m_literalBranch = true; + //controlTypes = m_context.getTypes(); + singleReturn = basicRun(); + + //if (controlTypes == m_context.getTypes()) + //break; + } +} + +/*void TypeAnalyzer::run_(const Type* blockType) { + if (m_graph.isEmpty()) + return; + m_blockType = blockType; // FIXME For correct inference we need to perform in-width traverse @@ -110,7 +250,7 @@ void TypeAnalyzer::run(const Type* blockType /*= 0*/) { std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; if (m_literalBranch && singleReturn) { - std::cout << "Return path was inferred literally. No need to perform induction run." << std::endl; + // std::cout << "Return path was inferred literally. No need to perform induction run." << std::endl; return; } @@ -132,7 +272,7 @@ void TypeAnalyzer::run(const Type* blockType /*= 0*/) { std::cout << "return type: " << m_context.getReturnType().toString() << std::endl; } -} +}*/ Type& TypeAnalyzer::getArgumentType(const InstructionNode& instruction, std::size_t index /*= 0*/) { ControlNode* const argNode = instruction.getArgument(index); @@ -162,14 +302,14 @@ void TypeAnalyzer::processInstruction(InstructionNode& instruction) { case opcode::sendBinary: doSendBinary(instruction); break; case opcode::sendMessage: doSendMessage(instruction); break; - case opcode::doPrimitive: doPrimitive(instruction); break; - case opcode::doSpecial: doSpecial(instruction); break; default: break; } + +// std::printf("type of %.2u is %s\n", instruction.getIndex(), m_context[instruction].toString().c_str()); } void TypeAnalyzer::doPushConstant(const InstructionNode& instruction) { @@ -337,6 +477,8 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { } InferContext* TypeAnalyzer::getMethodContext() { + assert(m_blockType); + for (TContextStack* stack = m_contextStack.parent; stack; stack = stack->parent) { const TInteger contextIndex((*m_blockType)[Type::bstContextIndex].getValue()); @@ -348,14 +490,28 @@ InferContext* TypeAnalyzer::getMethodContext() { } void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { - if (const TauNode* const tau = instruction.getTauNode()) { - const Type& tauType = m_context[*tau]; + if (TauNode* const tau = instruction.getTauNode()) { + switch (tau->getKind()) { + case TauNode::tkAggregator: + processTau(*tau); + case TauNode::tkProvider: + m_context[instruction] = m_context[*tau]; + break; - if (tau->getKind() == TauNode::tkAggregator) - processTau(*tau); + case TauNode::tkUnknown: + std::fprintf(stderr, "Unknown tau node index %u found from %u\n", tau->getIndex(), instruction.getIndex()); + assert(false); + break; - m_context[instruction] = tauType; + case TauNode::tkClosure: { + const ClosureTauNode* const closureTau = tau->cast(); + const uint32_t closureIndex = closureTau->getOrigin()->getIndex(); + const uint16_t tempIndex = instruction.getInstruction().getArgument(); + m_context[instruction] = m_context.getBlockClosures()[closureIndex][tempIndex]; + break; + } + } } else if (m_blockType) { const uint16_t argIndex = TInteger((*m_blockType)[Type::bstArgIndex].getValue()); const uint16_t tempIndex = instruction.getInstruction().getArgument(); @@ -365,14 +521,14 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { } else { if (InferContext* const methodContext = getMethodContext()) { const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); - InferContext::TTypeMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + InferContext::TVariableMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; m_context[instruction] = closureTypes[tempIndex]; } } } else { // Method variables are initialized to nil by default - m_context[instruction] = Type(Type::tkPolytype); + m_context[instruction] = Type(globals.nilObject); } } @@ -381,26 +537,35 @@ void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { assert(tau); assert(tau->getKind() == TauNode::tkProvider); - const ControlNode& argument = *instruction.getArgument(); - m_context[*tau] = m_context[argument]; + const ControlNode* const argument = instruction.getArgument(); + m_context[*tau] = m_context[*argument]; if (!m_blockType) return; InferContext* const methodContext = getMethodContext(); - if (!methodContext) + if (!methodContext) { + std::cout << "method context not found for " << instruction.getIndex() << std::endl; return; + } const TInteger captureIndex((*m_blockType)[Type::bstCaptureIndex].getValue()); - InferContext::TTypeMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; + InferContext::TVariableMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; const uint16_t tempIndex = instruction.getInstruction().getArgument(); - InferContext::TTypeMap::iterator iType = closureTypes.find(tempIndex); + InferContext::TVariableMap::iterator iType = closureTypes.find(tempIndex); if (iType == closureTypes.end()) - closureTypes[tempIndex] = m_context[argument]; + closureTypes[tempIndex] = m_context[*argument]; else - closureTypes[tempIndex] &= m_context[argument]; + closureTypes[tempIndex] &= m_context[*argument]; + + std::cout + << "doAssignTemporary, writing temporary " << tempIndex + << " in closure " << captureIndex.getValue() + << " type & " << m_context[*argument].toString() + << " = " << closureTypes[tempIndex].toString() + << std::endl; } void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { @@ -411,6 +576,11 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { Type& blockType = m_context[instruction]; + if (!blockType.getSubTypes().empty()) + return; // Already processed before + + m_graph.getMeta().hasLiteralBlocks = true; + blockType.set(globals.blockClass, Type::tkMonotype); blockType.pushSubType(origin); // [Type::bstOrigin] blockType.pushSubType(Type(TInteger(offset))); // [Type::bstOffset] @@ -425,6 +595,9 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; const TIndexList& writesTemporaries = blockGraph->getMeta().writesTemporaries; + if (writesTemporaries.size()) + m_graph.getMeta().hasMutatingBlocks = true; + Type readIndices(globals.arrayClass, Type::tkArray); Type writeIndices(globals.arrayClass, Type::tkArray); @@ -452,71 +625,135 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { class TypeLocator : public GraphWalker { public: TypeLocator(std::size_t tempIndex, const ControlGraph::TEdgeSet& backEdges, InferContext& context, bool noBackEdges) - : tempIndex(tempIndex), backEdges(backEdges), context(context), noBackEdges(noBackEdges), firstResult(true) {} - + : m_tempIndex(tempIndex), m_backEdges(backEdges), m_context(context), m_noBackEdges(noBackEdges), m_firstResult(true) {} + const Type& getResult() const { return m_result; } +private: virtual TVisitResult visitNode(st::ControlNode& node, const TPathNode* path) { if (InstructionNode* const instruction = node.cast()) { - // TODO Also handle previously discovered non-trivial assign sites - - if (instruction->getInstruction().getOpcode() == opcode::assignTemporary) { - if (instruction->getInstruction().getArgument() == tempIndex) { - TauNode* const tau = instruction->getTauNode(); - if (! tau) - return vrSkipPath; - - // Searching for back edges in the located path - bool hasBackEdge = false; - for (const TPathNode* p = path; p->prev; p = p->prev) { - const ControlGraph::TEdgeSet::const_iterator iEdge = backEdges.find( - st::BackEdgeDetector::TEdge( - static_cast(p->node), - static_cast(p->prev->node) - ) - ); - - if (iEdge != backEdges.end()) { - hasBackEdge = true; - break; - } - } - - std::printf("TypeLocator : Found assign site: Node %.2u :: %s, back edge: %s\n", - instruction->getIndex(), - context[*tau].toString().c_str(), - hasBackEdge ? "yes" : "no"); - - if (hasBackEdge && noBackEdges) - return vrSkipPath; - - if (firstResult) { - result = context[*tau]; - firstResult = false; - } else { - result &= context[*tau]; - } - - return vrSkipPath; - } + switch (instruction->getInstruction().getOpcode()) { + case opcode::assignTemporary: + return checkAssignTemporary(*instruction, path); + + case opcode::sendBinary: + case opcode::sendMessage: + return checkClosure(*instruction, path); + + default: + break; } } return vrKeepWalking; } - const TSmalltalkInstruction::TArgument tempIndex; - const ControlGraph::TEdgeSet& backEdges; - InferContext& context; - bool noBackEdges; + bool containsBackEdge(const TPathNode* path) const { + // Search for back edges in the located path + + for (const TPathNode* p = path; p->prev; p = p->prev) { + const ControlGraph::TEdgeSet::const_iterator iEdge = m_backEdges.find( + st::BackEdgeDetector::TEdge( + static_cast(p->node), + static_cast(p->prev->node) + ) + ); + + if (iEdge != m_backEdges.end()) + return true; + } + + return false; + } + + TVisitResult checkAssignTemporary(InstructionNode& instruction, const TPathNode* path) { + if (instruction.getInstruction().getArgument() != m_tempIndex) + return vrKeepWalking; + + TauNode* const tau = instruction.getTauNode(); + if (! tau) + return vrKeepWalking; + + // Searching for back edges in the located path + const bool viaBackEdge = containsBackEdge(path); + + std::printf("TypeLocator : Found assign site: Node %.2u :: %s, back edge: %s\n", + instruction.getIndex(), + m_context[*tau].toString().c_str(), + viaBackEdge ? "yes" : "no"); + + if (viaBackEdge && m_noBackEdges) + return vrSkipPath; + + if (m_firstResult) { + m_result = m_context[*tau]; + m_firstResult = false; + } else { + m_result &= m_context[*tau]; + } + + return vrSkipPath; + } + + TVisitResult checkClosure(InstructionNode& instruction, const TPathNode* path) { + std::cout << "checkClosure " << instruction.getIndex() << std::endl; + + const bool viaBackEdge = containsBackEdge(path); + + if (viaBackEdge && m_noBackEdges) + return vrSkipPath; + + const InferContext::TBlockClosures& closures = m_context.getBlockClosures(); + InferContext::TBlockClosures::const_iterator iClosure = closures.find(instruction.getIndex()); + + if (iClosure == closures.end()) { + std::cout << "checkClosure " << instruction.getIndex() << " is not found" << std::endl; + return vrKeepWalking; + } + + // FIXME Currently we link to a closure even if it is not mutating temporaries + + const InferContext::TVariableMap& closureVariables = iClosure->second; + InferContext::TVariableMap::const_iterator iType = closureVariables.find(m_tempIndex); + + if (iType == closureVariables.end()) { + std::cout << "checkClosure " << instruction.getIndex() << " no variable" << std::endl; + return vrKeepWalking; + } - Type result; - bool firstResult; + std::printf("TypeLocator : Found closure site: Node %.2u :: %s, back edge: %s\n", + instruction.getIndex(), + iType->second.toString().c_str(), + viaBackEdge ? "yes" : "no"); + + if (m_firstResult) { + m_result = iType->second; + m_firstResult = false; + } else { + m_result &= iType->second; + } + + return vrSkipPath; + } + +private: + const TSmalltalkInstruction::TArgument m_tempIndex; + const ControlGraph::TEdgeSet& m_backEdges; + InferContext& m_context; + const bool m_noBackEdges; + + Type m_result; + bool m_firstResult; }; void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) { TMethod* const currentMethod = m_graph.getParsedMethod()->getOrigin(); +// std::cout +// << "captureContext of " << instruction.getIndex() +// << ": Analyzing capture arguments " +// << arguments.toString() << std::endl; + // We interested in literal blocks with context info from the same method for (std::size_t argIndex = 0; argIndex < arguments.getSubTypes().size(); argIndex++) { Type& blockType = arguments[argIndex]; @@ -527,19 +764,32 @@ void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) if (blockType.getSubTypes().empty() || blockType[Type::bstOrigin].getValue() != currentMethod) continue; // Non-literal or non-local block - // Indexes of temporaries from the lexical context. See TypeAnalyzer::pushBlock() - const Type& readIndices = blockType[Type::bstReadsTemps]; + if (TInteger(blockType[Type::bstContextIndex].getValue()) != static_cast(m_context.getIndex())) + continue; // Block was created in another method (non-local) + + std::cout + << "captureContext of " << instruction.getIndex() + << " : Analyzing block " << blockType.toString() << std::endl; // Index of the capture site - if (readIndices.getSubTypes().size()) - blockType.pushSubType(Type(TInteger(instruction.getIndex()))); // [Type::bstCaptureIndex] + const InferContext::TSiteIndex siteIndex = instruction.getIndex(); + //FIXME if (blockType[Type::bstReadsTemps].getSubTypes().size()) + { + std::cout << "writing siteIndex: " << siteIndex << std::endl; + blockType.pushSubType(Type(TInteger(siteIndex))); // [Type::bstCaptureIndex] + + std::cout << "writing " << &instruction << " as site index " << siteIndex << std::endl; + m_siteMap[siteIndex] = &instruction; + } + + // Indexes of temporaries from the lexical context. See TypeAnalyzer::pushBlock() + const Type& readIndices = blockType[Type::bstReadsTemps]; // Prepare captured context by writing inferred variable types at the capture point for (std::size_t i = 0; i < readIndices.getSubTypes().size(); i++) { - // Detect types of temporaries accessible from the current call site - // TODO Move out of the loop const std::size_t variableIndex = TInteger(readIndices[i].getValue()); + // TODO Move out of the loop TypeLocator locator( variableIndex, // look for temporary with this index m_graph.getMeta().backEdges, @@ -547,15 +797,16 @@ void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) m_baseRun // base run = skip back edges ); - locator.run(&instruction, st::GraphWalker::wdBackward); + // Detect types of temporaries accessible from the current call site + locator.run(&instruction, st::GraphWalker::wdBackward, false); - InferContext::TTypeMap& typeMap = m_context.getBlockClosures()[instruction.getIndex()]; - InferContext::TTypeMap::iterator iType = typeMap.find(variableIndex); + InferContext::TVariableMap& typeMap = m_context.getBlockClosures()[siteIndex]; + InferContext::TVariableMap::iterator iType = typeMap.find(variableIndex); if (iType != typeMap.end()) - iType->second &= locator.result; + iType->second &= locator.getResult(); else - typeMap[variableIndex] = locator.result; + typeMap[variableIndex] = locator.getResult(); } } @@ -782,6 +1033,7 @@ Type& TypeAnalyzer::processPhi(const PhiNode& phi) { void TypeAnalyzer::processTau(const TauNode& tau) { Type& result = m_context[tau]; + const TauNode::TIncomingMap& incomings = tau.getIncomingMap(); TauNode::TIncomingMap::const_iterator iNode = incomings.begin(); @@ -801,6 +1053,8 @@ void TypeAnalyzer::processTau(const TauNode& tau) { result &= m_context[*iNode->first]; } } + + std::cout << "Tau value is " << result.toString() << std::endl; } void TypeAnalyzer::walkComplete() { @@ -820,13 +1074,13 @@ ControlGraph* TypeSystem::getControlGraph(TMethod* method) { entry.first = parsedMethod; entry.second = controlGraph; - { - std::ostringstream ss; - ss << method->klass->name->toString() + ">>" + method->name->toString(); - - ControlGraphVisualizer vis(controlGraph, ss.str(), "dots/"); - vis.run(); - } +// { +// std::ostringstream ss; +// ss << method->klass->name->toString() + ">>" + method->name->toString(); +// +// ControlGraphVisualizer vis(controlGraph, ss.str(), "dots/"); +// vis.run(); +// } return controlGraph; } @@ -879,14 +1133,14 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); contextMap[arguments] = inferContext; - ControlGraph* const methodGraph = getControlGraph(method); - assert(controlGraph); - std::printf("Analyzing %s::%s>>%s...\n", arguments.toString().c_str(), method->klass->name->toString().c_str(), selector->toString().c_str()); + ControlGraph* const methodGraph = getControlGraph(method); + assert(controlGraph); + // TODO Handle recursive and tail calls TContextStack contextStack(*inferContext, parent); type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); @@ -904,10 +1158,9 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments } InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContextStack* parent) { - if (block.getKind() != Type::tkMonotype || arguments.getSubTypes().empty()) + if (block.getKind() != Type::tkMonotype) return 0; - TMethod* const method = block[Type::bstOrigin].getValue()->cast(); const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); From 2ca6f8dad73d63f62453b8ba6e5b2e033d7d4a64 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 18 Jun 2016 18:08:28 +0600 Subject: [PATCH 051/105] Adds visualization of closure taus Issue: #17 --- src/ControlGraphVisualizer.cpp | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index e0f561b..6a2874f 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -156,6 +156,9 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { if (tau->getKind() == st::TauNode::tkProvider) { m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" << "weight=15 dir=back labelfloat=true color=\"red\" fontcolor=\"red\" style=\"dashed\" constraint=true ];\n"; +// } else if (tau->getKind() == st::TauNode::tkClosure) { +// m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" +// << "weight=15 dir=back labelfloat=true color=\"orange\" fontcolor=\"orange\" style=\"dashed\" constraint=true ];\n"; } else { const bool byBackEdge = iNode->second; m_stream << "\t\t" << iNode->first->getIndex() << " -> " << tau->getIndex() << " [" @@ -169,7 +172,23 @@ bool ControlGraphVisualizer::visitNode(st::ControlNode& node) { if ((*iNode)->getNodeType() == st::ControlNode::ntTau) continue; - m_stream << "\t\t" << tau->getIndex() << " -> " << (*iNode)->getIndex() << " [" + if (tau->getKind() == st::TauNode::tkClosure) { + if (static_cast(tau)->getOrigin() == *iNode) { + m_stream << "\t\t"; + + if (tau->getIncomingMap().empty()) + m_stream << (*iNode)->getIndex() << " -> " << tau->getIndex(); + else + m_stream << tau->getIndex() << " -> " << (*iNode)->getIndex(); + + m_stream << " [ weight=25 dir=back labelfloat=true color=\"orange\" fontcolor=\"orange\" style=\"dashed\" constraint=true ];\n"; + + continue; + } + } + + m_stream + << "\t\t" << tau->getIndex() << " -> " << (*iNode)->getIndex() << " [" << "weight=15 dir=back labelfloat=true color=\"green\" fontcolor=\"green\" style=\"dashed\" constraint=true ];\n"; } } @@ -208,7 +227,17 @@ void ControlGraphVisualizer::markNode(st::ControlNode* node) { case st::ControlNode::ntTau: label = "Tau "; - color = (node->cast()->getKind() == st::TauNode::tkProvider) ? "red" : "green"; + + switch (node->cast()->getKind()) { + case st::TauNode::tkProvider: color = "red"; break; + case st::TauNode::tkClosure: color = "orange"; break; + + case st::TauNode::tkAggregator: + default: + color = "green"; break; + break; + } + shape = "oval"; break; From 4b13dbb4479e32c5c9bdc5d53575f1ff6637f581 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 18 Jun 2016 20:30:41 +0600 Subject: [PATCH 052/105] Fixes asserts Issue: #17 --- src/TauLinker.cpp | 4 ++-- src/TypeAnalyzer.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp index 17ce7bc..a91773f 100644 --- a/src/TauLinker.cpp +++ b/src/TauLinker.cpp @@ -415,10 +415,10 @@ void TauLinker::processClosure(InstructionNode& instruction) { TAssignSiteList::const_iterator iAssignSite = locator.assignSites.begin(); for (; iAssignSite != locator.assignSites.end(); ++iAssignSite) { InstructionNode* const assignNode = (*iAssignSite).instruction->cast(); - assert(assignTemporary); + assert(assignNode); TauNode* const assignTau = assignNode->getTauNode(); - assert(assignType); + assert(assignTau); if ((*iAssignSite).byBackEdge) getGraph().getMeta().hasBackEdgeTau = true; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 59f9fd5..c807db9 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1139,7 +1139,7 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments selector->toString().c_str()); ControlGraph* const methodGraph = getControlGraph(method); - assert(controlGraph); + assert(methodGraph); // TODO Handle recursive and tail calls TContextStack contextStack(*inferContext, parent); @@ -1168,7 +1168,7 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); ControlGraph* const methodGraph = getControlGraph(method); - assert(controlGraph); + assert(methodGraph); std::printf("Analyzing block %s::%s ...\n", arguments.toString().c_str(), block.toString().c_str()); From ed478208a4df0f99251fdb3293a78a3822c38a77 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 19 Jun 2016 00:16:22 +0600 Subject: [PATCH 053/105] Adds handling of (Smallint) in arithmetic primitives Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index c807db9..9b59ed7 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -939,6 +939,7 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { { const Type& self = m_context[*instruction.getArgument(0)]; const Type& arg = m_context[*instruction.getArgument(1)]; + static const Type smallInt(globals.smallIntClass, Type::tkMonotype); if (isSmallInteger(self.getValue()) && isSmallInteger(arg.getValue())) { const int lhs = TInteger(self.getValue()).getValue(); @@ -959,10 +960,10 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { primitiveResult = (lhs < rhs) ? globals.trueObject : globals.falseObject; break; } + } else if ((self & smallInt) == smallInt && (arg & smallInt) == smallInt) { + primitiveResult = smallInt; } else { - // TODO Check for (SmallInt) primitiveResult = Type(Type::tkPolytype); - // primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); } break; From f78ba2442d6534a00b2ff14dcb702ec717f17d12 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 19 Jun 2016 01:11:48 +0600 Subject: [PATCH 054/105] Fixes block invoke primitive Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 9b59ed7..ec50b34 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -915,10 +915,10 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { Type& block = m_context[*instruction.getArgument(0)]; Type arguments(Type::tkArray); - if (instruction.getArgumentsCount() == 2) + if (instruction.getArgumentsCount() > 1) arguments.pushSubType(m_context[*instruction.getArgument(1)]); - if (instruction.getArgumentsCount() == 3) + if (instruction.getArgumentsCount() > 2) arguments.pushSubType(m_context[*instruction.getArgument(2)]); if (InferContext* const blockContext = m_system.inferBlock(block, arguments, &m_contextStack)) From a61220129585e6b63bbe3ef258ea5e16266e7348 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 19 Jun 2016 16:29:07 +0600 Subject: [PATCH 055/105] Adds inference of the special::sendToSuper instruction Issue: #17 Issue: #92 --- include/inference.h | 10 +++++++--- src/TypeAnalyzer.cpp | 47 ++++++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/include/inference.h b/include/inference.h index 21d432f..a78b899 100644 --- a/include/inference.h +++ b/include/inference.h @@ -295,8 +295,12 @@ class TypeSystem { typedef TSymbol* TSelector; + InferContext* inferMessage( + TSelector selector, + const Type& arguments, + TContextStack* parent, + bool sendToSuper = false); - InferContext* inferMessage(TSelector selector, const Type& arguments, TContextStack* parent); InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); ControlGraph* getControlGraph(TMethod* method); @@ -354,10 +358,10 @@ class TypeAnalyzer { void doSendUnary(const InstructionNode& instruction); void doSendBinary(InstructionNode& instruction); void doMarkArguments(const InstructionNode& instruction); - void doSendMessage(InstructionNode& instruction); + void doSendMessage(InstructionNode& instruction, bool sendToSuper = false); void doPrimitive(const InstructionNode& instruction); - void doSpecial(const InstructionNode& instruction); + void doSpecial(InstructionNode& instruction); private: void captureContext(InstructionNode& instruction, Type& arguments); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index ec50b34..a8d7aca 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -812,9 +812,11 @@ void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) } } -void TypeAnalyzer::doSendMessage(InstructionNode& instruction) { +void TypeAnalyzer::doSendMessage(InstructionNode& instruction, bool sendToSuper /*= false*/) { TSymbolArray& literals = *m_graph.getParsedMethod()->getOrigin()->literals; - const uint32_t literalIndex = instruction.getInstruction().getArgument(); + const uint32_t literalIndex = sendToSuper ? + instruction.getInstruction().getExtra() : + instruction.getInstruction().getArgument(); TSymbol* const selector = literals[literalIndex]; Type& arguments = m_context[*instruction.getArgument()]; @@ -822,7 +824,7 @@ void TypeAnalyzer::doSendMessage(InstructionNode& instruction) { captureContext(instruction, arguments); Type& result = m_context[instruction]; - if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack, sendToSuper)) result = context->getReturnType(); else result = Type(Type::tkPolytype); @@ -972,11 +974,11 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { m_context.getReturnType().addSubType(primitiveResult); - // This should depend on the primitive inference outcome + // TODO This should depend on the primitive inference outcome m_walker.addStopNode(*instruction.getOutEdges().begin()); } -void TypeAnalyzer::doSpecial(const InstructionNode& instruction) { +void TypeAnalyzer::doSpecial(InstructionNode& instruction) { const TSmalltalkInstruction::TArgument argument = instruction.getInstruction().getArgument(); switch (argument) { @@ -1007,8 +1009,7 @@ void TypeAnalyzer::doSpecial(const InstructionNode& instruction) { break; case special::sendToSuper: - // For now, treat method call as * - m_context[instruction] = Type(Type::tkPolytype); + doSendMessage(instruction, true); break; case special::duplicate: @@ -1086,7 +1087,12 @@ ControlGraph* TypeSystem::getControlGraph(TMethod* method) { return controlGraph; } -InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments, TContextStack* parent) { +InferContext* TypeSystem::inferMessage( + TSelector selector, + const Type& arguments, + TContextStack* parent, + bool sendToSuper /*= false*/) +{ if (!selector || arguments.getKind() != Type::tkArray || arguments.getSubTypes().empty()) return 0; @@ -1109,16 +1115,18 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments TContextMap& contextMap = m_contextCache[selector]; - const TContextMap::iterator iContext = contextMap.find(arguments); - if (iContext != contextMap.end()) - return iContext->second; + if (! sendToSuper) { + const TContextMap::iterator iContext = contextMap.find(arguments); + if (iContext != contextMap.end()) + return iContext->second; + } TClass* receiver = 0; if (self.getKind() == Type::tkLiteral) { if (isSmallInteger(self.getValue())) receiver = globals.smallIntClass; - else if (self.getValue()->getClass()->getClass() == globals.stringClass->getClass()->getClass()) + else if (self.getValue()->getClass() == globals.stringClass->getClass()->getClass()) receiver = self.getValue()->cast(); else receiver = self.getValue()->getClass(); @@ -1126,18 +1134,27 @@ InferContext* TypeSystem::inferMessage(TSelector selector, const Type& arguments receiver = self.getValue()->cast(); } + if (sendToSuper) + receiver = receiver->parentClass; + TMethod* const method = m_vm.lookupMethod(selector, receiver); - if (! method) // TODO Redirect to #doesNotUnderstand: statically + if (! method) { // TODO Redirect to #doesNotUnderstand: statically + std::printf("Lookup failed for %s::?>>%s...\n", + arguments.toString().c_str(), + selector->toString().c_str()); + return 0; + } InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); contextMap[arguments] = inferContext; - std::printf("Analyzing %s::%s>>%s...\n", + std::printf("Analyzing %s::%s>>%s...\n%s!\n", arguments.toString().c_str(), method->klass->name->toString().c_str(), - selector->toString().c_str()); + selector->toString().c_str(), + method->text->toString().c_str()); ControlGraph* const methodGraph = getControlGraph(method); assert(methodGraph); From d3d9262226872030bba70a9cb619249a325f44a3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 20 Jun 2016 14:22:48 +0600 Subject: [PATCH 056/105] Fixes ControlGraph::eraseTauNodes() Issue: #17 Issue: #92 --- src/ControlGraph.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index a962890..85ecf78 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -808,9 +808,18 @@ void ControlGraph::eraseTauNodes() { if (traces_enabled) std::printf("Erasing tau %.2u\n", tau->getIndex()); + const TauNode::TIncomingMap& incomings = tau->getIncomingMap(); + TauNode::TIncomingMap::const_iterator iIncoming = incomings.begin(); + for (; iIncoming != incomings.end(); ++iIncoming) { + if (InstructionNode* const instruction = iIncoming->first->cast()) + instruction->setTauNode(0); + + iIncoming->first->removeConsumer(tau); + } + const TNodeSet& consumers = tau->getConsumers(); - for (TNodeSet::iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { - if (InstructionNode* const instruction = (*iNode)->cast()) { + for (TNodeSet::const_iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { + if (InstructionNode* const instruction = (*iConsumer)->cast()) { assert(instruction->getTauNode() == tau); instruction->setTauNode(0); } From 197197bdcbf8c5a1ae4283780e3239db75e0fab8 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 20 Jun 2016 14:23:40 +0600 Subject: [PATCH 057/105] Fixes TauLinker Issue: #17 Issue: #92 --- src/TauLinker.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp index a91773f..6964b48 100644 --- a/src/TauLinker.cpp +++ b/src/TauLinker.cpp @@ -318,6 +318,9 @@ void TauLinker::detectRedundantTau() { } void TauLinker::createType(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + TauNode* const tau = getGraph().newNode(); tau->setKind(TauNode::tkProvider); tau->addIncoming(&instruction); @@ -337,6 +340,9 @@ void TauLinker::createType(InstructionNode& instruction) { } void TauLinker::processPushTemporary(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + // Searching for all AssignTemporary's and closures that provide a value for current node AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges(), getClosures()); locator.run(&instruction, GraphWalker::wdBackward, false); @@ -380,6 +386,9 @@ void TauLinker::processPushTemporary(InstructionNode& instruction) { } void TauLinker::processClosure(InstructionNode& instruction) { + if (instruction.getTauNode()) + return; + if (traces_enabled) std::printf("Analyzing closure %.2u\n", instruction.getIndex()); From 9b79157ad012d02a7c84c199efe642c4b657a968 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 23 Jun 2016 11:08:35 +0600 Subject: [PATCH 058/105] Fixes inference of the recurring contexts Issue: #17 Issue: #92 --- include/inference.h | 7 ++++++- src/TypeAnalyzer.cpp | 20 ++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/include/inference.h b/include/inference.h index a78b899..a9d7370 100644 --- a/include/inference.h +++ b/include/inference.h @@ -254,7 +254,12 @@ class InferContext { const TTypeMap& getTypes() const { return m_types; } void resetTypes() { m_types.clear(); } - Type& getReturnType() { return m_returnType; } + Type& getRawReturnType() { return m_returnType; } + + const Type& getReturnType() const { + const std::size_t subtypesCount = m_returnType.getSubTypes().size(); + return (subtypesCount == 1) ? m_returnType[0] : m_returnType; + } Type& getInstructionType(TNodeIndex index) { return m_types[index]; } Type& operator[] (TNodeIndex index) { return m_types[index]; } diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index a8d7aca..662f8d7 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -146,15 +146,7 @@ bool TypeAnalyzer::basicRun() { m_walker.resetStopNodes(); m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); - Type& returnType = m_context.getReturnType(); - const bool singleReturn = (returnType.getSubTypes().size() == 1); - - if (singleReturn) - returnType = returnType[0]; - else - returnType.setKind(Type::tkComposite); - - return singleReturn; + return m_context.getRawReturnType().getSubTypes().size() == 1; } std::string TypeAnalyzer::getMethodName() { @@ -972,7 +964,7 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { } } - m_context.getReturnType().addSubType(primitiveResult); + m_context.getRawReturnType().addSubType(primitiveResult); // TODO This should depend on the primitive inference outcome m_walker.addStopNode(*instruction.getOutEdges().begin()); @@ -1001,11 +993,11 @@ void TypeAnalyzer::doSpecial(InstructionNode& instruction) { } case special::stackReturn: - m_context.getReturnType().addSubType(getArgumentType(instruction)); + m_context.getRawReturnType().addSubType(getArgumentType(instruction)); break; case special::selfReturn: - m_context.getReturnType().addSubType(m_context.getArgument(0)); + m_context.getRawReturnType().addSubType(m_context.getArgument(0)); break; case special::sendToSuper: @@ -1164,7 +1156,7 @@ InferContext* TypeSystem::inferMessage( type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); analyzer.run(); - Type& returnType = inferContext->getReturnType(); + const Type& returnType = inferContext->getReturnType(); std::printf("%s::%s>>%s -> %s\n", arguments.toString().c_str(), @@ -1210,7 +1202,7 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex analyzer.run(&block); std::printf("%s::%s -> %s\n", arguments.toString().c_str(), block.toString().c_str(), - inferContext->getReturnType().toString().c_str()); + inferContext->getRawReturnType().toString().c_str()); return inferContext; } From 649ebe717a430a2c27eb9c008b23dcb08683228a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 23 Jun 2016 16:43:33 +0600 Subject: [PATCH 059/105] Adds TRecursionKind to the InferContext Issue: #17 Issue: #92 --- include/inference.h | 19 +++++++++++++++++-- src/TypeAnalyzer.cpp | 23 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/include/inference.h b/include/inference.h index a9d7370..e46bf63 100644 --- a/include/inference.h +++ b/include/inference.h @@ -235,8 +235,13 @@ typedef std::map TTypeMap; class InferContext { public: - InferContext(TMethod* method, std::size_t index, const Type& arguments) - : m_method(method), m_index(index), m_arguments(arguments) {} + InferContext(TMethod* method, std::size_t index, const Type& arguments) : + m_method(method), + m_index(index), + m_arguments(arguments), + m_returnType(Type::tkComposite), + m_recursionKind(rkUnknown) + {} TMethod* getMethod() const { return m_method; } std::size_t getIndex() const { return m_index; } @@ -276,6 +281,15 @@ class InferContext { TBlockClosures& getBlockClosures() { return m_blockClosures; } void resetClosures() { m_blockClosures.clear(); } + enum TRecursionKind { + rkUnknown = 0, + rkYes, + rkNo + }; + + TRecursionKind getRecursionKind() const { return m_recursionKind; } + void setRecursionKind(TRecursionKind value) { m_recursionKind = value; } + private: TMethod* const m_method; const std::size_t m_index; @@ -284,6 +298,7 @@ class InferContext { Type m_returnType; TBlockClosures m_blockClosures; + TRecursionKind m_recursionKind; }; struct TContextStack { diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 662f8d7..10df396 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1109,8 +1109,27 @@ InferContext* TypeSystem::inferMessage( if (! sendToSuper) { const TContextMap::iterator iContext = contextMap.find(arguments); - if (iContext != contextMap.end()) - return iContext->second; + if (iContext != contextMap.end()) { + InferContext* const cachedContext = iContext->second; + + if (cachedContext->getRecursionKind() == InferContext::rkUnknown) { + for (TContextStack* stack = parent; stack; stack = stack->parent) { + if (stack->context.getIndex() == cachedContext->getIndex()) { + std::printf("*** Context %s::%s>>%s recursively calls itself!\n", + cachedContext->getArguments().toString().c_str(), + cachedContext->getMethod()->klass->name->toString().c_str(), + selector->toString().c_str()); + + cachedContext->setRecursionKind(InferContext::rkYes); + return cachedContext; + } + } + + cachedContext->setRecursionKind(InferContext::rkNo); + } + + return cachedContext; + } } TClass* receiver = 0; From 6a0b389d6c8da5d4f7adc959ba9377a978074202 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 23 Jun 2016 16:44:47 +0600 Subject: [PATCH 060/105] Adds TypeSystem::dumpAllContexts() Issue: #17 Issue: #92 --- include/inference.h | 2 ++ src/TypeAnalyzer.cpp | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/inference.h b/include/inference.h index e46bf63..c501ddc 100644 --- a/include/inference.h +++ b/include/inference.h @@ -325,6 +325,8 @@ class TypeSystem { ControlGraph* getControlGraph(TMethod* method); + void dumpAllContexts() const; + private: typedef std::pair TGraphEntry; typedef std::map TGraphCache; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 10df396..451c69b 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1225,3 +1225,25 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex return inferContext; } + +void type::TypeSystem::dumpAllContexts() const { + std::cout << "Full context dump: " << std::endl; + + TContextCache::const_iterator iSelectorMap = m_contextCache.begin(); + for (; iSelectorMap != m_contextCache.end(); ++iSelectorMap) { + const TSymbol* const selector = iSelectorMap->first; + + TContextMap::const_iterator iContext = iSelectorMap->second.begin(); + for (; iContext != iSelectorMap->second.end(); ++iContext) { + const Type& arguments = iContext->first; + const InferContext* const context = iContext->second; + + std::printf("%s::%s>>%s -> %s\n", + arguments.toString().c_str(), + context->getMethod()->klass->name->toString().c_str(), + selector->toString().c_str(), + context->getReturnType().toString().c_str()); + } + } +} + From 5fd6a7dd56c204ac5f3326a51874a702033be8fc Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 23 Jun 2016 16:47:28 +0600 Subject: [PATCH 061/105] Adds TypeAnalyzer::dumpTypes() Issue: #17 Issue: #92 --- include/inference.h | 1 + src/TypeAnalyzer.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/inference.h b/include/inference.h index c501ddc..80ec6ef 100644 --- a/include/inference.h +++ b/include/inference.h @@ -357,6 +357,7 @@ class TypeAnalyzer { void run(const Type* blockType = 0); private: + void dumpTypes(const InferContext& context); std::string getMethodName(); bool basicRun(); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 451c69b..2525d8d 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -159,6 +159,13 @@ std::string TypeAnalyzer::getMethodName() { return ss.str(); } +void TypeAnalyzer::dumpTypes(const InferContext& context) { + TTypeMap::const_iterator iType = context.getTypes().begin(); + for (; iType != context.getTypes().end(); ++iType) + std::cout << iType->first << " " << iType->second.toString() << std::endl; + + std::cout << "Return type: " << context.getReturnType().toString() << std::endl; +} void TypeAnalyzer::run(const Type* blockType /*= 0*/) { if (m_graph.isEmpty()) From aa9070565f455091bff931037aebc3cce22825cd Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 25 Jun 2016 01:03:43 +0600 Subject: [PATCH 062/105] Fixes Type::operator |= and TypeAnalyzer::processPhi() Issue: #17 Issue: #92 --- include/inference.h | 28 ++++++++++++++++++++-------- src/TypeAnalyzer.cpp | 29 ++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/include/inference.h b/include/inference.h index 80ec6ef..2c7c831 100644 --- a/include/inference.h +++ b/include/inference.h @@ -19,6 +19,7 @@ class Type { tkComposite, tkArray, tkPolytype + // TODO tkBlock }; enum TBlockSubtypes { @@ -123,24 +124,35 @@ class Type { Type operator | (const Type& other) const { return Type(*this) |= other; } Type operator & (const Type& other) const { return Type(*this) &= other; } + // ? | _ -> _ + // * | _ -> (*, _) + + // 1 | 1 -> 1 + // 1 | 2 -> (1, 2) + // A | B -> (A, B) + // (A) | (B) -> (A, B) + // (A) | (B,C) -> (A, B, C) + // Block1 | Block2 -> (Block1, Block2) Type& operator |= (const Type& other) { if (*this == other) return *this; - // FIXME May lose information - if (m_kind == tkUndefined) - return *this = other; - if (m_kind != tkComposite) { - m_subTypes.push_back(*this); - m_kind = tkComposite; + Type composite(tkComposite); + composite.addSubType(*this); + *this = composite; } - if (other.m_kind != tkComposite) { + if (other.m_value == globals.blockClass) { addSubType(other); - } else { + return *this; + } + + if (other.m_kind == tkComposite) { for (std::size_t index = 0; index < other.m_subTypes.size(); index++) addSubType(other[index]); + + return *this; } return *this; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 2525d8d..f4ae6e4 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -468,8 +468,19 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { result.reset(); for (std::size_t index = 0; index < instruction.getArgumentsCount(); index++) { - const Type& argument = m_context[*instruction.getArgument(index)]; - result.pushSubType(argument); + ControlNode* const argument = instruction.getArgument(index); + TTypeMap::const_iterator iType = m_context.getTypes().find(argument->getIndex()); + + if (iType == m_context.getTypes().end()) { + if (PhiNode* const phi = argument->cast()) + processPhi(*phi); + + const Type& type = m_context[*argument]; + result.pushSubType(type); + + } else { + result.pushSubType(iType->second); + } } result.set(globals.arrayClass, Type::tkArray); @@ -1019,6 +1030,7 @@ void TypeAnalyzer::doSpecial(InstructionNode& instruction) { Type& TypeAnalyzer::processPhi(const PhiNode& phi) { Type& result = m_context[phi]; + bool first = true; const TNodeSet& incomings = phi.getRealValues(); TNodeSet::iterator iNode = incomings.begin(); @@ -1026,7 +1038,18 @@ Type& TypeAnalyzer::processPhi(const PhiNode& phi) { // FIXME We need to track the source of the phi's incoming. // We may ignore tkUndefined only if node lies on the dead path. - result |= m_context[*(*iNode)->cast()]; + const InstructionNode* const incoming = (*iNode)->cast(); + const TauNode* const incomingTau = incoming->getTauNode(); + const Type& incomingType = incomingTau ? m_context[*incomingTau] : m_context[*incoming]; + + if (first) { + first = false; + result = incomingType; + } else { + std::cout << "phi: " << result.toString() << " | " << incomingType.toString(); + result |= incomingType; + std::cout << " = " << result.toString() << std::endl; + } } return result; From 881c62b904cfdc43a68f9ccf81fd40e96dc715b5 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 26 Jun 2016 11:41:10 +0600 Subject: [PATCH 063/105] Fixes TypeAnalyzer::pushTemporary() by using getArgumentType() Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f4ae6e4..2c8c43c 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -547,8 +547,8 @@ void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { assert(tau); assert(tau->getKind() == TauNode::tkProvider); - const ControlNode* const argument = instruction.getArgument(); - m_context[*tau] = m_context[*argument]; + const Type& argumentType = getArgumentType(instruction); + m_context[*tau] = argumentType; if (!m_blockType) return; @@ -566,14 +566,14 @@ void TypeAnalyzer::doAssignTemporary(const InstructionNode& instruction) { InferContext::TVariableMap::iterator iType = closureTypes.find(tempIndex); if (iType == closureTypes.end()) - closureTypes[tempIndex] = m_context[*argument]; + closureTypes[tempIndex] = argumentType; else - closureTypes[tempIndex] &= m_context[*argument]; + closureTypes[tempIndex] &= argumentType; std::cout << "doAssignTemporary, writing temporary " << tempIndex << " in closure " << captureIndex.getValue() - << " type & " << m_context[*argument].toString() + << " type & " << argumentType.toString() << " = " << closureTypes[tempIndex].toString() << std::endl; } From c08afa675ae753f2e76e9d6e23b453964468d428 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 26 Jun 2016 11:45:01 +0600 Subject: [PATCH 064/105] Adds more primitives to TypeAnalyzer::doPrimitive() Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 2c8c43c..35ee0ca 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -846,6 +846,18 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { Type primitiveResult; switch (opcode) { + case primitive::objectsAreEqual: { + Type& object1 = m_context[*instruction.getArgument(0)]; + Type& object2 = m_context[*instruction.getArgument(1)]; + + if (object1.getKind() == Type::tkLiteral && object2.getKind() == Type::tkLiteral) + primitiveResult = Type(object1.getValue() == object2.getValue() ? globals.trueObject : globals.falseObject); + else + primitiveResult = Type(globals.trueObject->getClass()->parentClass, Type::tkMonotype); + + break; + } + case primitive::allocateObject: case primitive::allocateByteArray: { @@ -923,6 +935,16 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { break; } + case primitive::arrayAt: + case primitive::arrayAtPut: + case primitive::bulkReplace: + primitiveResult = Type(Type::tkPolytype); + break; + + case primitive::cloneByteObject: + primitiveResult = m_context[*instruction.getArgument(0)]; + break; + case primitive::blockInvoke: { Type& block = m_context[*instruction.getArgument(0)]; Type arguments(Type::tkArray); From 663dd9c354c0dc6c7883f6abc74ff8c8b8ac5f80 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 26 Jun 2016 11:46:23 +0600 Subject: [PATCH 065/105] Fixes TypeAnalyzer::processPhi() Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 35ee0ca..7fabb32 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1052,7 +1052,7 @@ void TypeAnalyzer::doSpecial(InstructionNode& instruction) { Type& TypeAnalyzer::processPhi(const PhiNode& phi) { Type& result = m_context[phi]; - bool first = true; + bool typeAssigned = false; const TNodeSet& incomings = phi.getRealValues(); TNodeSet::iterator iNode = incomings.begin(); @@ -1064,8 +1064,8 @@ Type& TypeAnalyzer::processPhi(const PhiNode& phi) { const TauNode* const incomingTau = incoming->getTauNode(); const Type& incomingType = incomingTau ? m_context[*incomingTau] : m_context[*incoming]; - if (first) { - first = false; + if (! typeAssigned) { + typeAssigned = true; result = incomingType; } else { std::cout << "phi: " << result.toString() << " | " << incomingType.toString(); @@ -1074,6 +1074,9 @@ Type& TypeAnalyzer::processPhi(const PhiNode& phi) { } } + if (result.getSubTypes().size() == 1) + result = result[0]; + return result; } From 93142ef751077c4416e22af8143287c8b88a3cb6 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 26 Jun 2016 15:38:20 +0600 Subject: [PATCH 066/105] Adds InferContext::getSingleReturnType() Issue: #17 Issue: #92 --- include/inference.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/inference.h b/include/inference.h index 2c7c831..86fa9f3 100644 --- a/include/inference.h +++ b/include/inference.h @@ -278,6 +278,18 @@ class InferContext { return (subtypesCount == 1) ? m_returnType[0] : m_returnType; } + Type getSingleReturnType() const { + const std::size_t subtypesCount = m_returnType.getSubTypes().size(); + if (! subtypesCount) + return Type(); + + Type result = m_returnType.getSubTypes().at(0); + for (std::size_t i = 1; i < subtypesCount; i++) + result &= m_returnType.getSubTypes().at(i); + + return result; + } + Type& getInstructionType(TNodeIndex index) { return m_types[index]; } Type& operator[] (TNodeIndex index) { return m_types[index]; } Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } From 06b52e4119a3a377be54600930b1e911bd3655e2 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 28 Jun 2016 11:28:57 +0600 Subject: [PATCH 067/105] Fixes Type's comparison operator Issue: #17 Issue: #92 --- include/inference.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/inference.h b/include/inference.h index 86fa9f3..b36a759 100644 --- a/include/inference.h +++ b/include/inference.h @@ -95,6 +95,8 @@ class Type { for (std::size_t index = 0; index < m_subTypes.size(); index++) { if (m_subTypes[index] < other.m_subTypes[index]) return true; + else if (other.m_subTypes[index] < m_subTypes[index]) + return false; } return false; From 0f311b1ad7663f85dae1f5075ff3e7c77bf0bcf3 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 28 Jun 2016 11:33:04 +0600 Subject: [PATCH 068/105] Adds cross-context references This information may be used to analyze context relations as well as perform call graph walk-through. Issue: #17 Issue: #92 --- include/inference.h | 13 +++++++++++++ src/TypeAnalyzer.cpp | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/include/inference.h b/include/inference.h index b36a759..2f25c5b 100644 --- a/include/inference.h +++ b/include/inference.h @@ -316,6 +316,18 @@ class InferContext { TRecursionKind getRecursionKind() const { return m_recursionKind; } void setRecursionKind(TRecursionKind value) { m_recursionKind = value; } + + class ContextCompare { + public: + bool operator() (const InferContext* a, const InferContext* b) const { + return a->getIndex() < b->getIndex(); + } + }; + + typedef std::set TContextSet; + TContextSet& getReferredContexts() { return m_referredContexts; } + const TContextSet& getReferredContexts() const { return m_referredContexts; } + private: TMethod* const m_method; const std::size_t m_index; @@ -325,6 +337,7 @@ class InferContext { TBlockClosures m_blockClosures; TRecursionKind m_recursionKind; + TContextSet m_referredContexts; }; struct TContextStack { diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 7fabb32..64e00ae 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -455,10 +455,12 @@ void TypeAnalyzer::doSendBinary(InstructionNode& instruction) { captureContext(instruction, arguments); - if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) { result = context->getReturnType(); - else + m_context.getReferredContexts().insert(context); + } else { result = Type(Type::tkPolytype); + } } void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { @@ -834,10 +836,12 @@ void TypeAnalyzer::doSendMessage(InstructionNode& instruction, bool sendToSuper captureContext(instruction, arguments); Type& result = m_context[instruction]; - if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack, sendToSuper)) + if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack, sendToSuper)) { result = context->getReturnType(); - else + m_context.getReferredContexts().insert(context); + } else { result = Type(Type::tkPolytype); + } } void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { @@ -955,10 +959,12 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { if (instruction.getArgumentsCount() > 2) arguments.pushSubType(m_context[*instruction.getArgument(2)]); - if (InferContext* const blockContext = m_system.inferBlock(block, arguments, &m_contextStack)) + if (InferContext* const blockContext = m_system.inferBlock(block, arguments, &m_contextStack)) { primitiveResult = blockContext->getReturnType(); - else + m_context.getReferredContexts().insert(blockContext); + } else { primitiveResult = Type(Type::tkPolytype); + } break; } From c856ee5d73de59dc8d0f67615f690467597c9472 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 28 Jun 2016 11:39:16 +0600 Subject: [PATCH 069/105] Adds cacheing for block contexts and for send-to-super Issue: #17 Issue: #92 --- include/inference.h | 5 +++++ src/TypeAnalyzer.cpp | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/include/inference.h b/include/inference.h index 2f25c5b..ee715dd 100644 --- a/include/inference.h +++ b/include/inference.h @@ -373,11 +373,16 @@ class TypeSystem { typedef std::map TContextMap; typedef std::map TContextCache; + // [Block, Args] -> block context + typedef std::map TBlockCache; + private: SmalltalkVM& m_vm; // TODO Image must be enough TGraphCache m_graphCache; TContextCache m_contextCache; + TBlockCache m_blockCache; + std::size_t m_lastContextIndex; }; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 64e00ae..3927b82 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1168,10 +1168,15 @@ InferContext* TypeSystem::inferMessage( TContextMap& contextMap = m_contextCache[selector]; - if (! sendToSuper) { - const TContextMap::iterator iContext = contextMap.find(arguments); + Type key(Type::tkArray); + key.pushSubType(arguments); + key.pushSubType(sendToSuper ? globals.trueObject : globals.falseObject); + + { + const TContextMap::iterator iContext = contextMap.find(key); if (iContext != contextMap.end()) { InferContext* const cachedContext = iContext->second; + std::printf("*** Found cached context for key: %s, cache: %p\n", key.toString().c_str(), &contextMap); if (cachedContext->getRecursionKind() == InferContext::rkUnknown) { for (TContextStack* stack = parent; stack; stack = stack->parent) { @@ -1191,6 +1196,8 @@ InferContext* TypeSystem::inferMessage( return cachedContext; } + + std::printf("*** Not found cached context for key: %s, cache: %p\n", key.toString().c_str(), &contextMap); } TClass* receiver = 0; @@ -1220,9 +1227,10 @@ InferContext* TypeSystem::inferMessage( } InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); - contextMap[arguments] = inferContext; + contextMap[key] = inferContext; - std::printf("Analyzing %s::%s>>%s...\n%s!\n", + std::printf("(%u) Analyzing %s::%s>>%s ...\n%s!\n", + inferContext->getIndex(), arguments.toString().c_str(), method->klass->name->toString().c_str(), selector->toString().c_str(), @@ -1231,7 +1239,6 @@ InferContext* TypeSystem::inferMessage( ControlGraph* const methodGraph = getControlGraph(method); assert(methodGraph); - // TODO Handle recursive and tail calls TContextStack contextStack(*inferContext, parent); type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); analyzer.run(); @@ -1251,16 +1258,32 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex if (block.getKind() != Type::tkMonotype) return 0; + // FIXME Prove that this is enough. + // What about captured types/args? + Type key(Type::tkArray); + key.pushSubType(block); + key.pushSubType(arguments); + + TBlockCache::iterator iBlock = m_blockCache.find(key); + if (iBlock != m_blockCache.end()) + return iBlock->second; + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); - // TODO Cache - InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); + InferContext* inferContext = new InferContext(method, m_lastContextIndex++, arguments); + + m_blockCache[key] = inferContext; + std::printf("Cached block context %s -> %p (index %u), cache size %u\n", + key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); ControlGraph* const methodGraph = getControlGraph(method); assert(methodGraph); - std::printf("Analyzing block %s::%s ...\n", arguments.toString().c_str(), block.toString().c_str()); + std::printf("(%u) Analyzing block %s::%s ...\n", + inferContext->getIndex(), + arguments.toString().c_str(), + block.toString().c_str()); st::ParsedMethod* const parsedMethod = methodGraph->getParsedMethod(); st::ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByOffset(offset); From ecfb6a2b5f1250f1d0caa1318be9935eba632812 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 28 Jun 2016 11:40:12 +0600 Subject: [PATCH 070/105] Adds call graph visualizer Issue: #17 Issue: #92 --- include/inference.h | 2 +- src/TypeAnalyzer.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/include/inference.h b/include/inference.h index ee715dd..9dda751 100644 --- a/include/inference.h +++ b/include/inference.h @@ -361,10 +361,10 @@ class TypeSystem { bool sendToSuper = false); InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); - ControlGraph* getControlGraph(TMethod* method); void dumpAllContexts() const; + void drawCallGraph() const; private: typedef std::pair TGraphEntry; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 3927b82..e00696e 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1331,3 +1331,65 @@ void type::TypeSystem::dumpAllContexts() const { } } +void type::TypeSystem::drawCallGraph() const { + std::ofstream stream; + stream.open("dots/callgraph.dot", std::ios::out | std::ios::trunc); + + stream << "digraph G2 {\n rankdir=LR;\n"; + + typedef std::set TIndexSet; + TIndexSet parsedContexts; + + TContextCache::const_iterator iSelectorMap = m_contextCache.begin(); + for (; iSelectorMap != m_contextCache.end(); ++iSelectorMap) { + const TSymbol* const selector = iSelectorMap->first; + + TContextMap::const_iterator iContext = iSelectorMap->second.begin(); + for (; iContext != iSelectorMap->second.end(); ++iContext) { + const Type& arguments = iContext->first[0]; + const bool sendToSuper = iContext->first[1].getValue() == globals.trueObject; + const InferContext* const context = iContext->second; + + stream + << context->getIndex() << " [shape=\"box\" color=\"" + << (sendToSuper ? "blue" : "black") + << "\" label=\"" + << arguments.toString() << "::" + << context->getMethod()->klass->name->toString() << ">>" + << selector->toString().c_str() << "\n" + << "Index: " << context->getIndex() << ", Returns: " + << context->getReturnType().toString() << "\"];\n"; + + const InferContext::TContextSet& referers = context->getReferredContexts(); + InferContext::TContextSet::const_iterator iReferer = referers.begin(); + for (; iReferer != referers.end(); ++iReferer) { + stream << context->getIndex() << " -> " << (*iReferer)->getIndex() << ";\n"; + + } + } + } + + TBlockCache::const_iterator iBlock = m_blockCache.begin(); + for (; iBlock != m_blockCache.end(); ++iBlock) { + const Type& block = iBlock->first[0]; + const Type& arguments = iBlock->first[1]; + InferContext* blockContext = iBlock->second; + + // TODO Block return + stream + << blockContext->getIndex() << " [shape=\"box\" color=\"red\" label=\"" + << arguments.toString() << "::" + << block.toString() << "\n" + << "Index: " << blockContext->getIndex() << ", Returns: " + << blockContext->getReturnType().toString() << "\"];\n"; + + const InferContext::TContextSet& referers = blockContext->getReferredContexts(); + InferContext::TContextSet::const_iterator iReferer = referers.begin(); + for (; iReferer != referers.end(); ++iReferer) + stream << blockContext->getIndex() << " -> " << (*iReferer)->getIndex() << ";\n"; + + } + + stream << "}\n"; + stream.close(); +} From 3f8506585de7e167925f1bfa0a5fc7d472021382 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 1 Jul 2016 13:55:48 +0600 Subject: [PATCH 071/105] Adds Type::flatten() and uses it to flatten phi and method return types Issue: #17 Issue: #92 --- include/inference.h | 6 ++++++ src/TypeAnalyzer.cpp | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/inference.h b/include/inference.h index 9dda751..eec2569 100644 --- a/include/inference.h +++ b/include/inference.h @@ -79,6 +79,8 @@ class Type { m_subTypes.push_back(type); } + Type flatten() const; + const Type& operator [] (std::size_t index) const { return m_subTypes[index]; } Type& operator [] (std::size_t index) { return m_subTypes[index]; } @@ -238,6 +240,10 @@ class Type { return *this; } +private: + typedef std::set TTypeSet; + void flattenInto(TTypeSet& typeSet) const; + private: TKind m_kind; TObject* m_value; diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index e00696e..a49524e 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -80,6 +80,30 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { return stream.str(); } +Type Type::flatten() const { + if (m_kind != tkComposite) + return *this; + + TTypeSet typeSet; + flattenInto(typeSet); + + Type result(tkComposite); + for (TTypeSet::iterator iType = typeSet.begin(); iType != typeSet.end(); ++iType) + result.pushSubType(*iType); + + return result; +} + +void Type::flattenInto(TTypeSet& typeSet) const { + if (m_kind != tkComposite) { + typeSet.insert(*this); + return; + } + + for (std::size_t index = 0; index < m_subTypes.size(); index++) + m_subTypes[index].flattenInto(typeSet); +} + void TypeAnalyzer::fillLinkerClosures() { typedef InferContext::TBlockClosures::const_iterator TClosureIterator; InferContext::TBlockClosures& closures = m_context.getBlockClosures(); @@ -1082,6 +1106,8 @@ Type& TypeAnalyzer::processPhi(const PhiNode& phi) { if (result.getSubTypes().size() == 1) result = result[0]; + else + result = result.flatten(); return result; } @@ -1243,6 +1269,7 @@ InferContext* TypeSystem::inferMessage( type::TypeAnalyzer analyzer(*this, *methodGraph, contextStack); analyzer.run(); + inferContext->getRawReturnType() = inferContext->getRawReturnType().flatten(); const Type& returnType = inferContext->getReturnType(); std::printf("%s::%s>>%s -> %s\n", @@ -1304,6 +1331,8 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex type::TypeAnalyzer analyzer(*this, *blockGraph, contextStack); analyzer.run(&block); + inferContext->getRawReturnType() = inferContext->getRawReturnType().flatten(); + std::printf("%s::%s -> %s\n", arguments.toString().c_str(), block.toString().c_str(), inferContext->getRawReturnType().toString().c_str()); From 0bf6f4245d90d6fda63d377052ad725f885e124a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 1 Jul 2016 13:58:01 +0600 Subject: [PATCH 072/105] Adds handling for throwError, stringAt/Put and blockReturn Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index a49524e..f6f4fd3 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -874,6 +874,10 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { Type primitiveResult; switch (opcode) { + case primitive::throwError: + m_walker.addStopNode(*instruction.getOutEdges().begin()); + return; + case primitive::objectsAreEqual: { Type& object1 = m_context[*instruction.getArgument(0)]; Type& object2 = m_context[*instruction.getArgument(1)]; @@ -965,6 +969,8 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { case primitive::arrayAt: case primitive::arrayAtPut: + case primitive::stringAt: + case primitive::stringAtPut: case primitive::bulkReplace: primitiveResult = Type(Type::tkPolytype); break; @@ -1070,6 +1076,10 @@ void TypeAnalyzer::doSpecial(InstructionNode& instruction) { m_context.getRawReturnType().addSubType(m_context.getArgument(0)); break; + case special::blockReturn: + getMethodContext()->getRawReturnType().addSubType(getArgumentType(instruction)); + break; + case special::sendToSuper: doSendMessage(instruction, true); break; From 15d4d044d0bb54cc5e100abe5faabc701b03d6ac Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 1 Jul 2016 18:19:44 +0600 Subject: [PATCH 073/105] Adds breadth-first graph traversing strategy Issue: #17 Issue: #92 --- include/analysis.h | 73 ++++++++++++++++++++++++++++++++++++++---- src/MethodCompiler.cpp | 4 +-- src/TauLinker.cpp | 4 +-- src/TypeAnalyzer.cpp | 4 +-- 4 files changed, 72 insertions(+), 13 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index e461043..b0d7a9a 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -698,17 +698,43 @@ class GraphWalker { wdBackward }; - void run(ControlNode* startNode, TWalkDirection direction, bool visitStart = true) { + enum TWalkType { + wtDepthFirst, + wtBreadthFirst + }; + + void run(ControlNode* startNode, TWalkDirection direction, TWalkType type, bool visitStart) { assert(startNode); m_direction = direction; + m_type = type; - TPathNode path(startNode); + if (type == wtDepthFirst) + depthRun(*startNode, visitStart); + else + breadthRun(*startNode, visitStart); - if (visitStart && visitNode(*startNode, &path) != vrKeepWalking) + nodesVisited(); + } + +private: + void depthRun(ControlNode& startNode, bool visitStart) { + TPathNode path(&startNode); + + if (visitStart && visitNode(startNode, &path) != vrKeepWalking) return; - walkIn(startNode, &path); - nodesVisited(); + walkIn(&startNode, &path); + } + + void breadthRun(ControlNode& startNode, bool visitStart) { + TNodeQueue queue; + + if (visitStart) + queue.push_back(&startNode); + else + enqueueNode(startNode, queue); + + walkQueue(queue); } protected: @@ -761,8 +787,41 @@ class GraphWalker { return true; } + typedef std::list TNodeQueue; + + void walkQueue(TNodeQueue& queue) { + while (! queue.empty()) { + ControlNode& currentNode = *queue.front(); queue.pop_front(); + + if (getNodeColor(¤tNode) == ncBlack) + continue; + + switch (visitNode(currentNode, 0)) { + case vrStopWalk: + return; + + case vrKeepWalking: + enqueueNode(currentNode, queue); + + case vrSkipPath: + break; + } + + m_colorMap[¤tNode] = ncBlack; + } + } + + void enqueueNode(const ControlNode& node, TNodeQueue& queue) { + const TNodeSet& nodes = (m_direction == wdForward) ? + node.getOutEdges() : node.getInEdges(); + + for (TNodeSet::const_iterator iNode = nodes.begin(); iNode != nodes.end(); ++iNode) + queue.push_back(*iNode); + } + private: TWalkDirection m_direction; + TWalkType m_type; TColorMap m_colorMap; }; @@ -778,7 +837,7 @@ class PathVerifier : public GraphWalker { assert(startNode); m_verified = false; - GraphWalker::run(startNode, wdForward); + GraphWalker::run(startNode, wdForward, wtDepthFirst, true); } private: @@ -812,7 +871,7 @@ class BackEdgeDetector : public GraphWalker { if (graph.nodes_begin() == graph.nodes_end()) return; - GraphWalker::run(*graph.nodes_begin(), GraphWalker::wdForward); + GraphWalker::run(*graph.nodes_begin(), GraphWalker::wdForward, wtDepthFirst, true); } protected: diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index c6ed339..af7ec5b 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -206,7 +206,7 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) }; GCDetector detector; - detector.run((*jit.controlGraph->begin())->getEntryPoint(), GCDetector::wdForward); + detector.run((*jit.controlGraph->begin())->getEntryPoint(), GCDetector::wdForward, GCDetector::wtDepthFirst, true); return detector.isDetected(); } @@ -268,7 +268,7 @@ bool MethodCompiler::shouldProtectProducer(st::ControlNode* producer) for (st::TNodeSet::iterator iConsumer = consumers.begin(); iConsumer != consumers.end(); ++iConsumer) { st::ControlNode* const consumer = *iConsumer; - walker.run(consumer, st::GraphWalker::wdBackward); + walker.run(consumer, st::GraphWalker::wdBackward, st::GraphWalker::wtDepthFirst, true); if (detector.isDetected()) { return true; diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp index 6964b48..ec51670 100644 --- a/src/TauLinker.cpp +++ b/src/TauLinker.cpp @@ -345,7 +345,7 @@ void TauLinker::processPushTemporary(InstructionNode& instruction) { // Searching for all AssignTemporary's and closures that provide a value for current node AssignLocator locator(instruction.getInstruction().getArgument(), getBackEdges(), getClosures()); - locator.run(&instruction, GraphWalker::wdBackward, false); + locator.run(&instruction, GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); TauNode* aggregator = 0; if (locator.assignSites.size() > 1) { @@ -412,7 +412,7 @@ void TauLinker::processClosure(InstructionNode& instruction) { for (ClosureTauNode::TIndex index = 0; index < closure.readIndices.size(); index++) { // Searching for all AssignTemporary's that provide a value for current node AssignLocator locator(closure.readIndices[index], getBackEdges(), getClosures()); - locator.run(&instruction, GraphWalker::wdBackward, false); + locator.run(&instruction, GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); TauNode* aggregator = 0; if (locator.assignSites.size() > 1) { diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f6f4fd3..542d24f 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -168,7 +168,7 @@ void TypeAnalyzer::fillLinkerClosures() { bool TypeAnalyzer::basicRun() { m_walker.resetStopNodes(); - m_walker.run(*m_graph.nodes_begin(), Walker::wdForward); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward, GraphWalker::wtDepthFirst, true); return m_context.getRawReturnType().getSubTypes().size() == 1; } @@ -834,7 +834,7 @@ void TypeAnalyzer::captureContext(InstructionNode& instruction, Type& arguments) ); // Detect types of temporaries accessible from the current call site - locator.run(&instruction, st::GraphWalker::wdBackward, false); + locator.run(&instruction, st::GraphWalker::wdBackward, GraphWalker::wtDepthFirst, false); InferContext::TVariableMap& typeMap = m_context.getBlockClosures()[siteIndex]; InferContext::TVariableMap::iterator iType = typeMap.find(variableIndex); From 5b7bcb56e297dc4905778ac1b4f95178dfee0975 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 1 Jul 2016 18:22:00 +0600 Subject: [PATCH 074/105] Uses breadth-first node walking in TypeAnalyzer::basicRun() For correct inference of the parallel paths in a graph it is critical to perform traverse breadth-first. Otherwise, phi nodes may have their incomings undefined. Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 542d24f..28f3969 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -168,7 +168,7 @@ void TypeAnalyzer::fillLinkerClosures() { bool TypeAnalyzer::basicRun() { m_walker.resetStopNodes(); - m_walker.run(*m_graph.nodes_begin(), Walker::wdForward, GraphWalker::wtDepthFirst, true); + m_walker.run(*m_graph.nodes_begin(), Walker::wdForward, GraphWalker::wtBreadthFirst, true); return m_context.getRawReturnType().getSubTypes().size() == 1; } From 829046758add99d933e32fec18437e1de57406c1 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 2 Jul 2016 01:11:53 +0600 Subject: [PATCH 075/105] Adds special handling of messages that do not return normally Some methods like Object>>error: do not return a value in a usual way. In that case control flow is interrupted and all call chain aborts. Type analyzer uses an empty composite type () to mark such special case. Please note that () is not equal to * or ? types that still return a value. Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 28f3969..294e6b9 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -482,6 +482,9 @@ void TypeAnalyzer::doSendBinary(InstructionNode& instruction) { if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack)) { result = context->getReturnType(); m_context.getReferredContexts().insert(context); + + if (result == Type(Type::tkComposite)) + m_walker.addStopNode(*instruction.getOutEdges().begin()); } else { result = Type(Type::tkPolytype); } @@ -863,6 +866,9 @@ void TypeAnalyzer::doSendMessage(InstructionNode& instruction, bool sendToSuper if (InferContext* const context = m_system.inferMessage(selector, arguments, &m_contextStack, sendToSuper)) { result = context->getReturnType(); m_context.getReferredContexts().insert(context); + + if (result == Type(Type::tkComposite)) + m_walker.addStopNode(*instruction.getOutEdges().begin()); } else { result = Type(Type::tkPolytype); } From ce9f0b7a661645c42fdd00fbbb51baf51237b823 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 2 Jul 2016 23:34:30 +0600 Subject: [PATCH 076/105] Adds Type::fold(), refactors getSingleReturnType() Issue: #17 Issue: #92 --- include/inference.h | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/include/inference.h b/include/inference.h index eec2569..812d635 100644 --- a/include/inference.h +++ b/include/inference.h @@ -81,6 +81,25 @@ class Type { Type flatten() const; + Type fold() const { + if (m_kind != tkComposite) + return *this; + + const std::size_t subtypesCount = m_subTypes.size(); + if (! subtypesCount) + return *this; + + Type result = m_subTypes.at(0); + for (std::size_t i = 1; i < subtypesCount; i++) { + if (m_subTypes.at(i).m_kind == tkComposite) + result &= m_subTypes.at(i).fold(); + else + result &= m_subTypes.at(i); + } + + return result; + } + const Type& operator [] (std::size_t index) const { return m_subTypes[index]; } Type& operator [] (std::size_t index) { return m_subTypes[index]; } @@ -286,17 +305,7 @@ class InferContext { return (subtypesCount == 1) ? m_returnType[0] : m_returnType; } - Type getSingleReturnType() const { - const std::size_t subtypesCount = m_returnType.getSubTypes().size(); - if (! subtypesCount) - return Type(); - - Type result = m_returnType.getSubTypes().at(0); - for (std::size_t i = 1; i < subtypesCount; i++) - result &= m_returnType.getSubTypes().at(i); - - return result; - } + Type getSingleReturnType() const { return m_returnType.fold(); } Type& getInstructionType(TNodeIndex index) { return m_types[index]; } Type& operator[] (TNodeIndex index) { return m_types[index]; } From d74c752150610f788aabce37600b084cc9f3027a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 6 Jul 2016 00:37:37 +0600 Subject: [PATCH 077/105] Adds caching for block graphs, renames getControlGraph() to getMethodGraph() Issue: #17 Issue: #92 --- include/inference.h | 16 ++++++++++------ src/TypeAnalyzer.cpp | 31 +++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/include/inference.h b/include/inference.h index 812d635..fd86851 100644 --- a/include/inference.h +++ b/include/inference.h @@ -376,7 +376,8 @@ class TypeSystem { bool sendToSuper = false); InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); - ControlGraph* getControlGraph(TMethod* method); + ControlGraph* getMethodGraph(TMethod* method); + ControlGraph* getBlockGraph(st::ParsedBlock* parsedBlock); void dumpAllContexts() const; void drawCallGraph() const; @@ -391,14 +392,17 @@ class TypeSystem { // [Block, Args] -> block context typedef std::map TBlockCache; + typedef std::map TBlockGraphCache; + private: - SmalltalkVM& m_vm; // TODO Image must be enough + SmalltalkVM& m_vm; // TODO Image must be enough - TGraphCache m_graphCache; - TContextCache m_contextCache; - TBlockCache m_blockCache; + TGraphCache m_graphCache; + TContextCache m_contextCache; + TBlockCache m_blockCache; + TBlockGraphCache m_blockGraphCache; - std::size_t m_lastContextIndex; + std::size_t m_lastContextIndex; }; class TypeAnalyzer { diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 294e6b9..d67e58b 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -518,7 +518,7 @@ void TypeAnalyzer::doMarkArguments(const InstructionNode& instruction) { InferContext* TypeAnalyzer::getMethodContext() { assert(m_blockType); - for (TContextStack* stack = m_contextStack.parent; stack; stack = stack->parent) { + for (TContextStack* stack = &m_contextStack; stack; stack = stack->parent) { const TInteger contextIndex((*m_blockType)[Type::bstContextIndex].getValue()); if (stack->context.getIndex() == static_cast(contextIndex)) @@ -626,9 +626,8 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { blockType.pushSubType(Type(TInteger(argIndex))); // [Type::bstArgIndex] blockType.pushSubType(Type(TInteger(m_context.getIndex()))); // [Type::bstContextIndex] - // TODO Cache and reuse in TypeSystem::inferBlock() - ControlGraph* const blockGraph = new ControlGraph(pushBlock->getParsedBlock()->getContainer(), pushBlock->getParsedBlock()); - blockGraph->buildGraph(); + ControlGraph* const blockGraph = m_system.getBlockGraph(pushBlock->getParsedBlock()); + assert(blockGraph); typedef ControlGraph::TMetaInfo::TIndexList TIndexList; const TIndexList& readsTemporaries = blockGraph->getMeta().readsTemporaries; @@ -1158,7 +1157,7 @@ void TypeAnalyzer::walkComplete() { // std::printf("walk complete\n"); } -ControlGraph* TypeSystem::getControlGraph(TMethod* method) { +ControlGraph* TypeSystem::getMethodGraph(TMethod* method) { TGraphCache::iterator iGraph = m_graphCache.find(method); if (iGraph != m_graphCache.end()) return iGraph->second.second; @@ -1182,6 +1181,19 @@ ControlGraph* TypeSystem::getControlGraph(TMethod* method) { return controlGraph; } +ControlGraph* TypeSystem::getBlockGraph(st::ParsedBlock* parsedBlock) { + TBlockGraphCache::const_iterator iGraph = m_blockGraphCache.find(parsedBlock); + if (iGraph != m_blockGraphCache.end()) + return iGraph->second; + + ControlGraph* const graph = new ControlGraph(parsedBlock->getContainer(), parsedBlock); + graph->buildGraph(); + + m_blockGraphCache[parsedBlock] = graph; + + return graph; +} + InferContext* TypeSystem::inferMessage( TSelector selector, const Type& arguments, @@ -1278,7 +1290,7 @@ InferContext* TypeSystem::inferMessage( selector->toString().c_str(), method->text->toString().c_str()); - ControlGraph* const methodGraph = getControlGraph(method); + ControlGraph* const methodGraph = getMethodGraph(method); assert(methodGraph); TContextStack contextStack(*inferContext, parent); @@ -1320,7 +1332,7 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex std::printf("Cached block context %s -> %p (index %u), cache size %u\n", key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); - ControlGraph* const methodGraph = getControlGraph(method); + ControlGraph* const methodGraph = getMethodGraph(method); assert(methodGraph); std::printf("(%u) Analyzing block %s::%s ...\n", @@ -1331,9 +1343,8 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex st::ParsedMethod* const parsedMethod = methodGraph->getParsedMethod(); st::ParsedBlock* const parsedBlock = parsedMethod->getParsedBlockByOffset(offset); - // TODO Cache - ControlGraph* const blockGraph = new ControlGraph(parsedMethod, parsedBlock); - blockGraph->buildGraph(); + ControlGraph* const blockGraph = getBlockGraph(parsedBlock); + assert(blockGraph); { std::ostringstream ss; From 31ff87fb3c6ffc5a33b39494131dc75a70777fbf Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Wed, 6 Jul 2016 00:39:21 +0600 Subject: [PATCH 078/105] Folds arguments of SmallInt primitives Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index d67e58b..e40e739 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1012,8 +1012,8 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { case primitive::smallIntEqual: case primitive::smallIntLess: { - const Type& self = m_context[*instruction.getArgument(0)]; - const Type& arg = m_context[*instruction.getArgument(1)]; + const Type& self = m_context[*instruction.getArgument(0)].fold(); + const Type& arg = m_context[*instruction.getArgument(1)].fold(); static const Type smallInt(globals.smallIntClass, Type::tkMonotype); if (isSmallInteger(self.getValue()) && isSmallInteger(arg.getValue())) { From 63aacb2691cd094e98d6c418fc5c03d9c52ddd02 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 9 Jul 2016 10:56:35 +0600 Subject: [PATCH 079/105] Adds helper functions Type::is*() and getQualifiedName() Issue: #17 Issue: #92 --- include/inference.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/inference.h b/include/inference.h index fd86851..203da8a 100644 --- a/include/inference.h +++ b/include/inference.h @@ -70,6 +70,13 @@ class Type { m_value = klass; } + bool isUndefined() const { return m_kind == tkUndefined; } + bool isLiteral() const { return m_kind == tkLiteral; } + bool isMonotype() const { return m_kind == tkMonotype; } + bool isComposite() const { return m_kind == tkComposite; } + bool isArray() const { return m_kind == tkArray; } + bool isPolytype() const { return m_kind == tkPolytype; } + const TSubTypes& getSubTypes() const { return m_subTypes; } Type& pushSubType(const Type& type) { m_subTypes.push_back(type); return m_subTypes.back(); } @@ -272,6 +279,13 @@ class Type { typedef std::size_t TNodeIndex; typedef std::map TTypeMap; +inline std::string getQualifiedMethodName(TMethod* method, const Type& arguments) { + return + arguments.toString(true) + "::" + + method->getClass()->name->toString() + ">>" + + method->name->toString(); +} + class InferContext { public: InferContext(TMethod* method, std::size_t index, const Type& arguments) : @@ -282,6 +296,8 @@ class InferContext { m_recursionKind(rkUnknown) {} + std::string getQualifiedName() const { return getQualifiedMethodName(m_method, m_arguments); } + TMethod* getMethod() const { return m_method; } std::size_t getIndex() const { return m_index; } From eb8eeb9acb7124eb498e4da45990379eb21b4f5e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 9 Jul 2016 11:00:14 +0600 Subject: [PATCH 080/105] Refactors inference.h by namespaces by adding explicit st:: prefix Previously if one includes inference.h and uses namespace type, then it will result in a name collision if LLVM is in scope. Issue: #17 Issue: #92 --- include/inference.h | 61 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/include/inference.h b/include/inference.h index 203da8a..a71f8cf 100644 --- a/include/inference.h +++ b/include/inference.h @@ -8,7 +8,6 @@ namespace type { -using namespace st; class Type { public: @@ -325,7 +324,7 @@ class InferContext { Type& getInstructionType(TNodeIndex index) { return m_types[index]; } Type& operator[] (TNodeIndex index) { return m_types[index]; } - Type& operator[] (const ControlNode& node) { return getInstructionType(node.getIndex()); } + Type& operator[] (const st::ControlNode& node) { return getInstructionType(node.getIndex()); } // variable index -> aggregated type typedef std::size_t TVariableIndex; @@ -392,14 +391,14 @@ class TypeSystem { bool sendToSuper = false); InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); - ControlGraph* getMethodGraph(TMethod* method); - ControlGraph* getBlockGraph(st::ParsedBlock* parsedBlock); + st::ControlGraph* getMethodGraph(TMethod* method); + st::ControlGraph* getBlockGraph(st::ParsedBlock* parsedBlock); void dumpAllContexts() const; void drawCallGraph() const; private: - typedef std::pair TGraphEntry; + typedef std::pair TGraphEntry; typedef std::map TGraphCache; typedef std::map TContextMap; @@ -408,7 +407,7 @@ class TypeSystem { // [Block, Args] -> block context typedef std::map TBlockCache; - typedef std::map TBlockGraphCache; + typedef std::map TBlockGraphCache; private: SmalltalkVM& m_vm; // TODO Image must be enough @@ -423,7 +422,7 @@ class TypeSystem { class TypeAnalyzer { public: - TypeAnalyzer(TypeSystem& system, ControlGraph& graph, TContextStack& contextStack) : + TypeAnalyzer(TypeSystem& system, st::ControlGraph& graph, TContextStack& contextStack) : m_system(system), m_graph(graph), m_contextStack(contextStack), @@ -440,46 +439,46 @@ class TypeAnalyzer { std::string getMethodName(); bool basicRun(); - void processInstruction(InstructionNode& instruction); - void processTau(const TauNode& tau); + void processInstruction(st::InstructionNode& instruction); + void processTau(const st::TauNode& tau); - Type& processPhi(const PhiNode& phi); - Type& getArgumentType(const InstructionNode& instruction, std::size_t index = 0); + Type& processPhi(const st::PhiNode& phi); + Type& getArgumentType(const st::InstructionNode& instruction, std::size_t index = 0); void walkComplete(); - void doPushConstant(const InstructionNode& instruction); - void doPushLiteral(const InstructionNode& instruction); - void doPushArgument(const InstructionNode& instruction); + void doPushConstant(const st::InstructionNode& instruction); + void doPushLiteral(const st::InstructionNode& instruction); + void doPushArgument(const st::InstructionNode& instruction); - void doPushTemporary(const InstructionNode& instruction); - void doAssignTemporary(const InstructionNode& instruction); + void doPushTemporary(const st::InstructionNode& instruction); + void doAssignTemporary(const st::InstructionNode& instruction); - void doPushBlock(const InstructionNode& instruction); + void doPushBlock(const st::InstructionNode& instruction); - void doSendUnary(const InstructionNode& instruction); - void doSendBinary(InstructionNode& instruction); - void doMarkArguments(const InstructionNode& instruction); - void doSendMessage(InstructionNode& instruction, bool sendToSuper = false); + void doSendUnary(const st::InstructionNode& instruction); + void doSendBinary(st::InstructionNode& instruction); + void doMarkArguments(const st::InstructionNode& instruction); + void doSendMessage(st::InstructionNode& instruction, bool sendToSuper = false); - void doPrimitive(const InstructionNode& instruction); - void doSpecial(InstructionNode& instruction); + void doPrimitive(const st::InstructionNode& instruction); + void doSpecial(st::InstructionNode& instruction); private: - void captureContext(InstructionNode& instruction, Type& arguments); + void captureContext(st::InstructionNode& instruction, Type& arguments); InferContext* getMethodContext(); void fillLinkerClosures(); private: - class Walker : public GraphWalker { + class Walker : public st::GraphWalker { public: Walker(TypeAnalyzer& analyzer) : analyzer(analyzer) {} private: - TVisitResult visitNode(ControlNode& node, const TPathNode*) { - if (InstructionNode* const instruction = node.cast()) + TVisitResult visitNode(st::ControlNode& node, const TPathNode*) { + if (st::InstructionNode* const instruction = node.cast()) analyzer.processInstruction(*instruction); return vrKeepWalking; @@ -495,14 +494,14 @@ class TypeAnalyzer { private: TypeSystem& m_system; - ControlGraph& m_graph; + st::ControlGraph& m_graph; TContextStack& m_contextStack; InferContext& m_context; - TauLinker m_tauLinker; + st::TauLinker m_tauLinker; Walker m_walker; - typedef std::map TSiteMap; + typedef std::map TSiteMap; TSiteMap m_siteMap; bool m_baseRun; @@ -513,4 +512,4 @@ class TypeAnalyzer { } // namespace type -#endif +#endif // LLST_INFERENCE_H_INCLUDED From 40eb706b75529a81235ff5428d448542932b9555 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 9 Jul 2016 11:24:20 +0600 Subject: [PATCH 081/105] Adds initial inference logic to the MethodCompiler and JitRuntime Issue: #17 Issue: #92 --- include/inference.h | 12 +++ include/jit.h | 54 +++++++---- src/JITRuntime.cpp | 65 ++++++++++--- src/MethodCompiler.cpp | 204 ++++++++++++++++++----------------------- src/TypeAnalyzer.cpp | 1 + 5 files changed, 189 insertions(+), 147 deletions(-) diff --git a/include/inference.h b/include/inference.h index a71f8cf..b274e41 100644 --- a/include/inference.h +++ b/include/inference.h @@ -510,6 +510,18 @@ class TypeAnalyzer { const Type* m_blockType; }; +inline type::Type createArgumentsType(TObjectArray* arguments) { + type::Type result(globals.arrayClass, type::Type::tkArray); + + for (std::size_t i = 0; i < arguments->getSize(); i++) { + TObject* const argument = arguments->getField(i); + TClass* const klass = isSmallInteger(argument) ? globals.smallIntClass : argument->getClass(); + result.pushSubType(type::Type(klass)); + } + + return result; +} + } // namespace type #endif // LLST_INFERENCE_H_INCLUDED diff --git a/include/jit.h b/include/jit.h index d0a0002..50fdea5 100644 --- a/include/jit.h +++ b/include/jit.h @@ -35,6 +35,7 @@ #include #include "vm.h" #include "analysis.h" +#include "inference.h" #include @@ -206,6 +207,7 @@ class MethodCompiler { TPhiList pendingPhiNodes; TMethod* originMethod; // Smalltalk method we're currently processing + type::InferContext& inferContext; llvm::Function* function; // LLVM function that is created based on method llvm::IRBuilder<>* builder; // Builder inserts instructions into basic blocks @@ -228,10 +230,26 @@ class MethodCompiler { llvm::Value* getMethodClass(); llvm::Value* getLiteral(uint32_t index); - TJITContext(MethodCompiler* compiler, TMethod* method, bool parse = true) - : currentNode(0), originMethod(method), function(0), builder(0), - preamble(0), exceptionLandingPad(0), unwindBlockReturn(0), unwindPhi(0), methodHasBlockReturn(false), - methodAllocatesMemory(true), compiler(compiler), contextHolder(0), selfHolder(0) + TJITContext( + MethodCompiler* compiler, + TMethod* method, + type::InferContext& context, + bool parse = true + ) : + currentNode(0), + originMethod(method), + inferContext(context), + function(0), + builder(0), + preamble(0), + exceptionLandingPad(0), + unwindBlockReturn(0), + unwindPhi(0), + methodHasBlockReturn(false), + methodAllocatesMemory(true), + compiler(compiler), + contextHolder(0), + selfHolder(0) { if (parse) { parsedMethod = new st::ParsedMethod(method); @@ -254,7 +272,7 @@ class MethodCompiler { st::ParsedMethod* method, st::ParsedBlock* block ) - : TJITContext(compiler, 0, false), parsedBlock(block) + : TJITContext(compiler, 0, *(type::InferContext*)(0), false), parsedBlock(block) { parsedMethod = method; originMethod = parsedMethod->getOrigin(); @@ -282,6 +300,10 @@ class MethodCompiler { TExceptionAPI m_exceptionAPI; TBaseFunctions m_baseFunctions; +private: + type::TypeSystem m_typeSystem; + +private: llvm::Value* getNodeValue(TJITContext& jit, st::ControlNode* node, llvm::BasicBlock* insertBlock = 0); llvm::Value* getPhiValue(TJITContext& jit, st::PhiNode* phi); void encodePhiIncomings(TJITContext& jit, st::PhiNode* phiNode); @@ -316,7 +338,8 @@ class MethodCompiler { void doSendUnary(TJITContext& jit); void doSendBinary(TJITContext& jit); void doSendMessage(TJITContext& jit); - bool doSendMessageToLiteral(TJITContext& jit, st::InstructionNode* receiverNode, TClass* receiverClass = 0); + void doSendGenericMessage(TJITContext& jit); + void doSendInferredMessage(TJITContext& jit, type::InferContext& context); void doSpecial(TJITContext& jit); void doPrimitive(TJITContext& jit); @@ -334,7 +357,7 @@ class MethodCompiler { llvm::BasicBlock* primitiveFailedBB); TObjectAndSize createArray(TJITContext& jit, uint32_t elementsCount); - llvm::Function* createFunction(TMethod* method); + llvm::Function* createFunction(TMethod* method, const std::string& functionName); uint16_t getSkipOffset(st::InstructionNode* branch); @@ -349,6 +372,7 @@ class MethodCompiler { llvm::Function* compileMethod( TMethod* method, + const type::Type& arguments, llvm::Function* methodFunction = 0, llvm::Value** contextHolder = 0 ); @@ -371,14 +395,7 @@ class MethodCompiler { llvm::Module* JITModule, TRuntimeAPI runtimeApi, TExceptionAPI exceptionApi - ) - : m_runtime(runtime), m_JITModule(JITModule), - m_runtimeAPI(runtimeApi), m_exceptionAPI(exceptionApi), m_callSiteIndex(1) - { - m_baseTypes.initializeFromModule(JITModule); - m_globals.initializeFromModule(JITModule); - m_baseFunctions.initializeFromModule(JITModule); - } + ); }; @@ -446,9 +463,12 @@ class JITRuntime { friend TReturnValue invokeBlock(TBlock* block, TContext* callingContext); friend void emitBlockReturn(TObject* value, TContext* targetContext); + static const unsigned int ARG_CACHE_SIZE = 8; struct TFunctionCacheEntry { TMethod* method; + TClass* arguments[ARG_CACHE_SIZE]; + TMethodFunction function; }; @@ -472,9 +492,9 @@ class JITRuntime { uint32_t m_blockReturnsEmitted; uint32_t m_objectsAllocated; - TMethodFunction lookupFunctionInCache(TMethod* method); + TMethodFunction lookupFunctionInCache(TMethod* method, TObjectArray* arguments); TBlockFunction lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset); - void updateFunctionCache(TMethod* method, TMethodFunction function); + void updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments); void updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function); void flushBlockFunctionCache(); diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 5c92f4f..04bf01c 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -234,18 +234,34 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u return newBlock; } -JITRuntime::TMethodFunction JITRuntime::lookupFunctionInCache(TMethod* method) +JITRuntime::TMethodFunction JITRuntime::lookupFunctionInCache(TMethod* method, TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(method) ^ reinterpret_cast(method->name); // ^ 0xDEADBEEF; + if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) { + m_cacheMisses++; + return 0; + } + + const uint32_t hash = (reinterpret_cast(method) << 2) + arguments->getSize(); TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; - if (entry.method == method) { - m_cacheHits++; - return entry.function; - } else { + if (entry.method != method) { m_cacheMisses++; return 0; } + + for (std::size_t i = 0; i < arguments->getSize(); i++) { + TClass* const cachedClass = entry.arguments[i]; + TObject* const field = arguments->getField(i); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + m_cacheMisses++; + return 0; + } + } + + m_cacheHits++; + return entry.function; } JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset) @@ -262,13 +278,27 @@ JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TMethod* conta } } -void JITRuntime::updateFunctionCache(TMethod* method, TMethodFunction function) +void JITRuntime::updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(method) ^ reinterpret_cast(method->name); // ^ 0xDEADBEEF; + if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) + return; + + const std::size_t argSize = arguments->getSize(); + const uint32_t hash = (reinterpret_cast(method) << 2) + argSize; TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; entry.method = method; entry.function = function; + + for (std::size_t i = 0; i < argSize; i++) { + TObject* const field = arguments->getField(i); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + entry.arguments[i] = currentClass; + } + + if (argSize < ARG_CACHE_SIZE) + entry.arguments[argSize + 1] = 0; } void JITRuntime::updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function) @@ -371,16 +401,20 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject } // Searching for the jit compiled function - compiledMethodFunction = lookupFunctionInCache(method); + compiledMethodFunction = lookupFunctionInCache(method, messageArguments); if (! compiledMethodFunction) { + type::Type argumentsType = type::createArgumentsType(messageArguments); + if (receiverClass) + argumentsType[0].set(receiverClass); + // If function was not found in the cache looking it in the LLVM directly - std::string functionName = method->klass->name->toString() + ">>" + method->name->toString(); + const std::string& functionName = type::getQualifiedMethodName(method, argumentsType); Function* methodFunction = m_JITModule->getFunction(functionName); if (! methodFunction) { // Compiling function and storing it to the table for further use - methodFunction = m_methodCompiler->compileMethod(method); + methodFunction = m_methodCompiler->compileMethod(method, argumentsType); // outs() << *methodFunction << "\n"; @@ -391,7 +425,7 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject // Calling the method and returning the result compiledMethodFunction = reinterpret_cast(m_executionEngine->getPointerToFunction(methodFunction)); - updateFunctionCache(method, compiledMethodFunction); + updateFunctionCache(method, compiledMethodFunction, messageArguments); // outs() << *methodFunction << "\n"; @@ -401,7 +435,7 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject } // Updating call site statistics and scheduling method processing - updateHotSites(compiledMethodFunction, previousContext, message, receiverClass, callSiteIndex); + //updateHotSites(compiledMethodFunction, previousContext, message, receiverClass, callSiteIndex); // Preparing the context objects. Because we do not call the software // implementation here, we do not need to allocate the stack object @@ -409,6 +443,7 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject // initialization of various objects such as stackTop and bytePointer. // Creating context object and temporaries + // TODO Think about stack allocation hptr newTemps = m_softVM->newObject(method->temporarySize); newContext = m_softVM->newObject(); @@ -439,7 +474,7 @@ void JITRuntime::updateHotSites(TMethodFunction methodFunction, TContext* callin if (!callSiteIndex) return; - TMethodFunction callerMethodFunction = lookupFunctionInCache(callingContext->method); + TMethodFunction callerMethodFunction = lookupFunctionInCache(callingContext->method, 0); // TODO reload cache if callerMethodFunction was popped out if (!callerMethodFunction) @@ -494,7 +529,7 @@ void JITRuntime::patchHotMethods() // Compiling function from scratch outs() << "Recompiling method for patching: " << methodFunction->getName().str() << "\n"; Value* contextHolder = 0; - m_methodCompiler->compileMethod(method, methodFunction, &contextHolder); + m_methodCompiler->compileMethod(method, type::Type(), methodFunction, &contextHolder); outs() << "Patching " << hotMethod->methodFunction->getName().str() << " ..."; diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index af7ec5b..7f0a982 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include using namespace llvm; @@ -55,6 +56,24 @@ std::string to_string(const T& x) { return ss.str(); } +MethodCompiler::MethodCompiler( + JITRuntime& runtime, + llvm::Module* JITModule, + TRuntimeAPI runtimeApi, + TExceptionAPI exceptionApi +) : + m_runtime(runtime), + m_JITModule(JITModule), + m_runtimeAPI(runtimeApi), + m_exceptionAPI(exceptionApi), + m_typeSystem(*runtime.getVM()), + m_callSiteIndex(1) +{ + m_baseTypes.initializeFromModule(JITModule); + m_globals.initializeFromModule(JITModule); + m_baseFunctions.initializeFromModule(JITModule); +} + Value* MethodCompiler::TJITContext::getLiteral(uint32_t index) { Value* const literal = builder->CreateCall2( @@ -78,7 +97,7 @@ Value* MethodCompiler::TJITContext::getMethodClass() return klass; } -Function* MethodCompiler::createFunction(TMethod* method) +Function* MethodCompiler::createFunction(TMethod* method, const std::string& functionName) { Type* const methodParams[] = { m_baseTypes.context->getPointerTo() }; FunctionType* const functionType = FunctionType::get( @@ -87,7 +106,6 @@ Function* MethodCompiler::createFunction(TMethod* method) false // we're not dealing with vararg ); - std::string functionName = method->klass->name->toString() + ">>" + method->name->toString(); Function* const function = cast( m_JITModule->getOrInsertFunction(functionName, functionType)); function->setCallingConv(CallingConv::C); //Anyway C-calling conversion is default function->setGC("shadow-stack"); @@ -395,12 +413,24 @@ TObjectAndSize MethodCompiler::createArray(TJITContext& jit, uint32_t elementsCo return std::make_pair(arrayObject, arraySize); } -Function* MethodCompiler::compileMethod(TMethod* method, llvm::Function* methodFunction /*= 0*/, llvm::Value** contextHolder /*= 0*/) +Function* MethodCompiler::compileMethod(TMethod* method, const type::Type& arguments, llvm::Function* methodFunction /*= 0*/, llvm::Value** contextHolder /*= 0*/) { - TJITContext jit(this, method); + type::InferContext* const inferContext = m_typeSystem.inferMessage(method->name, arguments, 0); + assert(inferContext); + assert(inferContext->getMethod() == method); + + const std::string& methodName = type::getQualifiedMethodName(method, arguments); + printf("compiling method %s\n", methodName.c_str()); + + TJITContext jit(this, method, *inferContext); + + { + ControlGraphVisualizer vis(jit.controlGraph, methodName, "dots/"); + vis.run(); + } // Creating the function named as "Class>>method" or using provided one - jit.function = methodFunction ? methodFunction : createFunction(method); + jit.function = methodFunction ? methodFunction : createFunction(method, methodName); // Creating the preamble basic block and inserting it into the function // It will contain basic initialization code (args, temps and so on) @@ -774,17 +804,22 @@ void MethodCompiler::doPushBlock(TJITContext& jit) llvm::Function* MethodCompiler::compileBlock(TBlock* block) { - TJITContext methodContext(this, block->method); + // TODO + type::Type blockType; + type::Type arguments; + type::InferContext* const inferContext = m_typeSystem.inferBlock(blockType, arguments, 0); + + TJITContext blockContext(this, block->method, *inferContext); const uint16_t blockOffset = block->blockBytePointer; std::ostringstream ss; ss << block->method->klass->name->toString() << ">>" << block->method->name->toString() << "@" << blockOffset; const std::string blockFunctionName = ss.str(); - st::ParsedBlock* const parsedBlock = methodContext.parsedMethod->getParsedBlockByOffset(blockOffset); + st::ParsedBlock* const parsedBlock = blockContext.parsedMethod->getParsedBlockByOffset(blockOffset); assert(parsedBlock); - return compileBlock(methodContext, blockFunctionName, parsedBlock); + return compileBlock(blockContext, blockFunctionName, parsedBlock); } llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string& blockFunctionName, st::ParsedBlock* parsedBlock) @@ -792,6 +827,11 @@ llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string const uint16_t blockOffset = parsedBlock->getStartOffset(); TJITBlockContext blockContext(this, jit.parsedMethod, parsedBlock); + { + ControlGraphVisualizer vis(blockContext.controlGraph, blockFunctionName, "dots/"); + vis.run(); + } + std::vector blockParams; blockParams.push_back(m_baseTypes.block->getPointerTo()); // block object with context information @@ -1146,80 +1186,35 @@ void MethodCompiler::doSendBinary(TJITContext& jit) setNodeValue(jit, jit.currentNode, resultHolder); } - -bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNode* receiverNode, TClass* receiverClass /*= 0*/) +void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& context) { - // Optimized version of doSendMessage which takes into account that - // pending message should be sent to the literal receiver - // (either constant or a member of method literals). Literal receivers - // are encoded at the time of method compilation, so thier value and - // their class will not change over time. Moreover, actual values - // are known at compile time and may be used to lookup the actual - // method that should be invoked. - - // Locating message selector - TSymbolArray& literals = *jit.originMethod->literals; - TSymbol* const messageSelector = literals[jit.currentNode->getInstruction().getArgument()]; - - // Determining receiver class - if (!receiverClass) { - TObject* literalReceiver = 0; - - const st::TSmalltalkInstruction::TOpcode opcode = receiverNode->getInstruction().getOpcode(); - if (opcode == opcode::pushLiteral) { - literalReceiver = literals[receiverNode->getInstruction().getArgument()]; - } else if (opcode == opcode::pushConstant) { - const uint8_t constant = receiverNode->getInstruction().getArgument(); - switch(constant) { - case 0: case 1: case 2: case 3: case 4: - case 5: case 6: case 7: case 8: case 9: - literalReceiver = TInteger(constant); - break; - - case pushConstants::nil: literalReceiver = globals.nilObject; break; - case pushConstants::trueObject: literalReceiver = globals.trueObject; break; - case pushConstants::falseObject: literalReceiver = globals.falseObject; break; - } - } - - assert(literalReceiver); - receiverClass = isSmallInteger(literalReceiver) ? globals.smallIntClass : literalReceiver->getClass(); - } - - assert(receiverClass); + // Inferred messages are easy to dispatch because we know + // virtually everything about their execution context. + // Therefore we may encode direct method call and optimize + // all stuff like as GC roots or checks that is redundant. - // Locating a method suitable for a direct call - TMethod* const directMethod = m_runtime.getVM()->lookupMethod(messageSelector, receiverClass); + TMethod* const directMethod = context.getMethod(); - if (! directMethod) { - outs() << "Error! Could not lookup method for class " << receiverClass->name->toString() << ", selector " << messageSelector->toString() << "\n"; - return false; - } - - std::string directFunctionName = directMethod->klass->name->toString() + ">>" + messageSelector->toString(); + const std::string& directFunctionName = context.getQualifiedName(); Function* directFunction = m_JITModule->getFunction(directFunctionName); if (!directFunction) { // Compiling function and storing it to the table for further use - directFunction = compileMethod(directMethod); - -// outs() << *directFunction << "\n"; + directFunction = compileMethod(directMethod, context.getArguments()); - verifyFunction(*directFunction , llvm::AbortProcessAction); - - m_runtime.optimizeFunction(directFunction, false); + outs() << *directFunction << "\n"; } // Allocating context object and temporaries on the methodFunction's stack. // This operation does not affect garbage collector, so no pointer protection // is required. Moreover, this is operation is much faster than heap allocation. - const bool hasTemporaries = directMethod->temporarySize > 0; + const bool hasTemporaries = context.getMethod()->temporarySize > 0; const uint32_t contextSize = sizeof(TContext); const uint32_t tempsSize = hasTemporaries ? sizeof(TObjectArray) + sizeof(TObject*) * directMethod->temporarySize : 0; // Allocating stack space for objects and registering GC protection holder - MethodCompiler::TStackObject contextPair = allocateStackObject(*jit.builder, sizeof(TContext), 0); + TStackObject contextPair = allocateStackObject(*jit.builder, sizeof(TContext), 0); Value* const contextSlot = contextPair.objectSlot; Value* tempsSlot = 0; @@ -1237,7 +1232,7 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod false // volatile operation ); - if (hasTemporaries) + if (hasTemporaries) { jit.builder->CreateMemSet( tempsSlot, // destination address jit.builder->getInt8(0), // fill with zeroes @@ -1245,13 +1240,14 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod 0, // no alignment false // volatile operation ); + } // Initializing object fields // TODO Move the init sequence out of the block or check that it is correctly optimized in loops - Value* const newContextObject = jit.builder->CreateBitCast(contextSlot, m_baseTypes.object->getPointerTo(), "newContext."); - Value* const newTempsObject = hasTemporaries ? jit.builder->CreateBitCast(tempsSlot, m_baseTypes.object->getPointerTo(), "newTemps.") : 0; - Function* const setObjectSize = getBaseFunctions().setObjectSize; - Function* const setObjectClass = getBaseFunctions().setObjectClass; + Value* const newContextObject = jit.builder->CreateBitCast(contextSlot, m_baseTypes.object->getPointerTo(), "newContext."); + Value* const newTempsObject = hasTemporaries ? jit.builder->CreateBitCast(tempsSlot, m_baseTypes.object->getPointerTo(), "newTemps.") : 0; + Function* const setObjectSize = getBaseFunctions().setObjectSize; + Function* const setObjectClass = getBaseFunctions().setObjectClass; // Object size stored in the TSize field of any ordinary object contains // number of pointers except for the first two fields @@ -1285,27 +1281,12 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(3), contextObject); Value* const newContext = jit.builder->CreateBitCast(newContextObject, m_baseTypes.context->getPointerTo()); - Value* result = 0; - - if (jit.methodHasBlockReturn) { - // Creating basic block that will be branched to on normal invoke - BasicBlock* const nextBlock = BasicBlock::Create(m_JITModule->getContext(), "next.", jit.function); - jit.currentNode->getDomain()->getBasicBlock()->setEndValue(nextBlock); + Value* const result = jit.builder->CreateCall(directFunction, newContext); - // Performing a function invoke - result = jit.builder->CreateInvoke(directFunction, nextBlock, jit.exceptionLandingPad, newContext); - - // Switching builder to a new block - jit.builder->SetInsertPoint(nextBlock); - } else { - // Just calling the function. No block switching is required - result = jit.builder->CreateCall(directFunction, newContext); - } - - AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); - Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); - Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); - jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); +// AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); +// Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); +// Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); +// jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); Value* const targetContext = jit.builder->CreateExtractValue(result, 1, "targetContext"); Value* const isBlockReturn = jit.builder->CreateIsNotNull(targetContext); @@ -1318,43 +1299,36 @@ bool MethodCompiler::doSendMessageToLiteral(TJITContext& jit, st::InstructionNod Value* const resultObject = jit.builder->CreateExtractValue(result, 0, "resultObject"); Value* const resultHolder = protectProducerNode(jit, jit.currentNode, resultObject); setNodeValue(jit, jit.currentNode, resultHolder); - -// Value* const resultHolder = protectProducerNode(jit, jit.currentNode, result); -// setNodeValue(jit, jit.currentNode, resultHolder); - - return true; } void MethodCompiler::doSendMessage(TJITContext& jit) { + // FIXME Temporary hack until block inference + // is not handled in method compiler + if (! &jit.inferContext) { + doSendGenericMessage(jit); + return; + } - st::InstructionNode* const markArgumentsNode = jit.currentNode->getArgument()->cast(); - assert(markArgumentsNode); - - st::InstructionNode* const receiverNode = markArgumentsNode->getArgument()->cast(); - assert(receiverNode); + const type::Type& arguments = jit.inferContext[*jit.currentNode->getArgument()]; - const st::TSmalltalkInstruction instruction = receiverNode->getInstruction(); + if (arguments.isArray() && !arguments.getSubTypes().empty()) { + TSymbolArray& literals = *jit.originMethod->literals; + const uint32_t literalIndex = jit.currentNode->getInstruction().getArgument(); + TSymbol* const selector = literals[literalIndex]; - // In case of a literal receiver we may encode direct method call - if (instruction.getOpcode() == opcode::pushLiteral || - instruction.getOpcode() == opcode::pushConstant) - { - if (doSendMessageToLiteral(jit, receiverNode)) + type::InferContext* const messageContext = m_typeSystem.inferMessage(selector, arguments, 0, false); + if (messageContext) { + doSendInferredMessage(jit, *messageContext); return; - } - - // We may optimize send to self if clas does not have children - // TODO More optimization possible: send to super, if chiildren do not override selector - if (instruction.getOpcode() == opcode::pushArgument && instruction.getArgument() == 0) - { - TClass* const receiverClass = jit.originMethod->klass; - if (receiverClass->package == globals.nilObject) { - if (doSendMessageToLiteral(jit, receiverNode, receiverClass)) - return; } } + doSendGenericMessage(jit); +} + +void MethodCompiler::doSendGenericMessage(TJITContext& jit) +{ Value* const arguments = getArgument(jit); // jit.popValue(); // First of all we need to get the actual message selector diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index e40e739..2ac2dbb 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -2,6 +2,7 @@ #include using namespace type; +using namespace st; static void printBlock(const Type& blockType, std::stringstream& stream) { if (blockType.getSubTypes().size() < Type::bstCaptureIndex) { From c6385adac46ab807b24ab2e561c5560388c033c6 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 10 Jul 2016 13:44:06 +0600 Subject: [PATCH 082/105] Context and tempo are now stack allocated, SendBinary partly uses inference Issue: #17 Issue: #92 --- include/jit.h | 6 +- include/types.h | 5 ++ src/JITRuntime.cpp | 42 +++++------ src/MethodCompiler.cpp | 167 ++++++++++++++++++++++++----------------- 4 files changed, 129 insertions(+), 91 deletions(-) diff --git a/include/jit.h b/include/jit.h index 50fdea5..b5a3fa4 100644 --- a/include/jit.h +++ b/include/jit.h @@ -224,6 +224,7 @@ class MethodCompiler { llvm::Value* contextHolder; llvm::Value* selfHolder; + llvm::Value* temporaries; llvm::Value* getCurrentContext(); llvm::Value* getSelf(); @@ -249,7 +250,8 @@ class MethodCompiler { methodAllocatesMemory(true), compiler(compiler), contextHolder(0), - selfHolder(0) + selfHolder(0), + temporaries(0) { if (parse) { parsedMethod = new st::ParsedMethod(method); @@ -313,7 +315,7 @@ class MethodCompiler { llvm::Value* allocateRoot(TJITContext& jit, llvm::Type* type); llvm::Value* protectPointer(TJITContext& jit, llvm::Value* value); llvm::Value* protectProducerNode(TJITContext& jit, st::ControlNode* node, llvm::Value* value); - bool shouldProtectProducer(st::ControlNode* node); + bool shouldProtectProducer(TJITContext& jit, st::ControlNode* node); bool methodAllocatesMemory(TJITContext& jit); void writePreamble(TJITContext& jit, bool isBlock = false); diff --git a/include/types.h b/include/types.h index eab3527..0116758 100644 --- a/include/types.h +++ b/include/types.h @@ -287,6 +287,11 @@ typedef TArray TSymbolArray; // will hold temporary objects during the call dispatching and the pointers // to the current executing instruction and the stack top. struct TContext : public TObject { + explicit TContext(TClass* klass) : + TObject(sizeof(TContext) / sizeof(TObject*) - 2, klass, false), + bytePointer(0), + stackTop(0) {} + TMethod* method; TObjectArray* arguments; TObjectArray* temporaries; diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 04bf01c..9c14baa 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -380,24 +380,40 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject hptr messageArguments = m_softVM->newPointer(arguments); TMethodFunction compiledMethodFunction = 0; - TContext* newContext = 0; - hptr previousContext = m_softVM->newPointer(callingContext); + + // Preparing the context object. Because we do not call the software + // implementation here, we do not need to allocate the stack object + // because it is not used by JIT runtime. We also may skip the proper + // initialization of various objects such as stackTop and bytePointer. + // Note: temporary object will be allocated by the function on it's frame. + + TContext contextSlot(globals.contextClass); + contextSlot.arguments = messageArguments; + contextSlot.previousContext = callingContext; + hptr newContext = m_softVM->newPointer(&contextSlot); { // First of all we need to find the actual method object if (!receiverClass) { - TObject* receiver = messageArguments[0]; + TObject* const receiver = messageArguments[0]; receiverClass = isSmallInteger(receiver) ? globals.smallIntClass : receiver->getClass(); } // Searching for the actual method to be called hptr method = m_softVM->newPointer(m_softVM->lookupMethod(message, receiverClass)); + newContext->method = method; // Checking whether we found a method if (method == 0) { - // Oops. Method was not found. In this case we should send #doesNotUnderstand: message to the receiver + // Oops. Method was not found. In this case we should + // send #doesNotUnderstand: message to the receiver. + + // TODO Refactor the code to eliminate memory allocation + // This will allow to completely remove hptr's m_softVM->setupVarsForDoesNotUnderstand(method, messageArguments, message, receiverClass); - // Continuing the execution just as if #doesNotUnderstand: was the actual selector that we wanted to call + + // Continuing the execution just as if #doesNotUnderstand: + // was the actual selector that we wanted to call } // Searching for the jit compiled function @@ -436,22 +452,6 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject // Updating call site statistics and scheduling method processing //updateHotSites(compiledMethodFunction, previousContext, message, receiverClass, callSiteIndex); - - // Preparing the context objects. Because we do not call the software - // implementation here, we do not need to allocate the stack object - // because it is not used by JIT runtime. We also may skip the proper - // initialization of various objects such as stackTop and bytePointer. - - // Creating context object and temporaries - // TODO Think about stack allocation - hptr newTemps = m_softVM->newObject(method->temporarySize); - newContext = m_softVM->newObject(); - - // Initializing context variables - newContext->temporaries = newTemps; - newContext->arguments = messageArguments; - newContext->method = method; - newContext->previousContext = previousContext; } try { diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 7f0a982..cbbdd34 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -151,7 +151,7 @@ Value* MethodCompiler::protectPointer(TJITContext& jit, Value* value) Value* MethodCompiler::protectProducerNode(TJITContext& jit, st::ControlNode* node, Value* value) { - if (shouldProtectProducer(jit.currentNode)) + if (shouldProtectProducer(jit, jit.currentNode)) return protectPointer(jit, value); else return value; // return value as is @@ -229,8 +229,14 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) return detector.isDetected(); } -bool MethodCompiler::shouldProtectProducer(st::ControlNode* producer) +bool MethodCompiler::shouldProtectProducer(TJITContext& jit, st::ControlNode* producer) { + if (&jit.inferContext) { // FIXME Remove after block infer context is initialized + const type::Type& type = jit.inferContext[*jit.currentNode]; + if (type.isLiteral() || type.getValue() == globals.smallIntClass) + return false; + } + // We should protect the value by holder if consumer of this value is far away. // By far away we mean that it crosses the barrier of potential garbage collection. // For example if value is consumed right next to the point it was produced, then @@ -311,11 +317,8 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) context = jit.builder->CreateBitCast(blockContext, m_baseTypes.context->getPointerTo()); } - context->setName("contextParameter"); - // Protecting the context holder - jit.contextHolder = jit.methodAllocatesMemory ? protectPointer(jit, context) : context; - jit.contextHolder->setName("pContext"); + jit.contextHolder = context; // Storing self pointer Value* const pargs = jit.builder->CreateStructGEP(context, 2); @@ -325,14 +328,42 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) jit.selfHolder = jit.methodAllocatesMemory ? protectPointer(jit, self) : self; jit.selfHolder->setName("pSelf"); + + // Allocating context object and temporaries on the methodFunction's stack. + // This operation does not affect garbage collector, so no pointer protection + // is required. Moreover, this is operation is much faster than heap allocation. + const std::size_t tempsCount = jit.originMethod->temporarySize; + + if (tempsCount) { + const uint32_t tempsSize = sizeof(TObjectArray) + sizeof(TObject*) * tempsCount; + + MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), tempsCount); + + jit.builder->CreateMemSet( + tempsPair.objectSlot, // destination address + jit.builder->getInt8(0), // fill with zeroes + tempsSize, // size of object slot + 0, // no alignment + false // volatile operation + ); + + Value* const newTempsObject = jit.builder->CreateBitCast(tempsPair.objectSlot, m_baseTypes.object->getPointerTo(), "temps."); + + const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; + jit.builder->CreateCall2(getBaseFunctions().setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); + jit.builder->CreateCall2(getBaseFunctions().setObjectClass, newTempsObject, getJitGlobals().arrayClass); + + Value* const contextObject = jit.builder->CreateBitCast(context, m_baseTypes.object->getPointerTo()); + jit.builder->CreateCall3(getBaseFunctions().setObjectField, contextObject, jit.builder->getInt32(2), newTempsObject); + + jit.temporaries = newTempsObject; + } + } Value* MethodCompiler::TJITContext::getCurrentContext() { - if (methodAllocatesMemory) - return builder->CreateLoad(contextHolder, "context."); - else - return contextHolder; + return contextHolder; } Value* MethodCompiler::TJITContext::getSelf() @@ -703,10 +734,11 @@ void MethodCompiler::doPushTemporary(TJITContext& jit) { const uint8_t index = jit.currentNode->getInstruction().getArgument(); Value* const temporary = jit.builder->CreateCall2( - m_baseFunctions.getTemporary, - jit.getCurrentContext(), - jit.builder->getInt32(index) + m_baseFunctions.getObjectField, + jit.temporaries, + jit.builder->getInt32(index) ); + temporary->setName(std::string("temp") + to_string(index) + "."); Value* const holder = protectProducerNode(jit, jit.currentNode, temporary); @@ -904,10 +936,10 @@ void MethodCompiler::doAssignTemporary(TJITContext& jit) Value* const value = getArgument(jit); jit.builder->CreateCall3( - m_baseFunctions.setTemporary, - jit.getCurrentContext(), - jit.builder->getInt32(index), - value + m_baseFunctions.setObjectField, + jit.temporaries, + jit.builder->getInt32(index), + value ); } @@ -1073,29 +1105,52 @@ void MethodCompiler::setNodeValue(TJITContext& jit, st::ControlNode* node, llvm: void MethodCompiler::doSendBinary(TJITContext& jit) { + const type::Type& leftType = jit.inferContext[*jit.currentNode->getArgument(0)]; + const type::Type& rightType = jit.inferContext[*jit.currentNode->getArgument(1)]; + const type::Type& resultType = jit.inferContext[*jit.currentNode]; + + if (leftType.isLiteral() && rightType.isLiteral()) { + ConstantInt* const rawResult = jit.builder->getInt32(TInteger(resultType.getValue())); + Value* const result = jit.builder->CreateCall(m_baseFunctions.newInteger, rawResult); + setNodeValue(jit, jit.currentNode, result); + return; + } + + // Literal int or (SmallInt) monotype + const bool leftTypeIsInt = isSmallInteger(leftType.getValue()) || leftType.getValue() == globals.smallIntClass; + const bool rightTypeIsInt = isSmallInteger(rightType.getValue()) || rightType.getValue() == globals.smallIntClass; + const bool encodeDirectIntegers = leftTypeIsInt && rightTypeIsInt; + // 0, 1 or 2 for '<', '<=' or '+' respectively binaryBuiltIns::Operator opcode = static_cast(jit.currentNode->getInstruction().getArgument()); - Value* const rightValue = getArgument(jit, 1); // jit.popValue(); Value* const leftValue = getArgument(jit, 0); // jit.popValue(); + Value* const rightValue = getArgument(jit, 1); // jit.popValue(); + + BasicBlock* integersBlock = 0; + BasicBlock* sendBinaryBlock = 0; + BasicBlock* resultBlock = 0; - // Checking if values are both small integers - Value* const rightIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, rightValue); - Value* const leftIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, leftValue); - Value* const isSmallInts = jit.builder->CreateAnd(rightIsInt, leftIsInt); + if (! encodeDirectIntegers) { + // Checking if values are both small integers + Value* const leftIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, leftValue); + Value* const rightIsInt = jit.builder->CreateCall(m_baseFunctions.isSmallInteger, rightValue); + Value* const isSmallInts = jit.builder->CreateAnd(rightIsInt, leftIsInt); - BasicBlock* const integersBlock = BasicBlock::Create(m_JITModule->getContext(), "asIntegers.", jit.function); - BasicBlock* const sendBinaryBlock = BasicBlock::Create(m_JITModule->getContext(), "asObjects.", jit.function); - BasicBlock* const resultBlock = BasicBlock::Create(m_JITModule->getContext(), "result.", jit.function); + integersBlock = BasicBlock::Create(m_JITModule->getContext(), "asIntegers.", jit.function); + sendBinaryBlock = BasicBlock::Create(m_JITModule->getContext(), "asObjects.", jit.function); + resultBlock = BasicBlock::Create(m_JITModule->getContext(), "result.", jit.function); - // Depending on the contents we may either do the integer operations - // directly or create a send message call using operand objects - jit.builder->CreateCondBr(isSmallInts, integersBlock, sendBinaryBlock); + // Depending on the contents we may either do the integer operations + // directly or create a send message call using operand objects + jit.builder->CreateCondBr(isSmallInts, integersBlock, sendBinaryBlock); + + jit.builder->SetInsertPoint(integersBlock); + } // Now the integers part - jit.builder->SetInsertPoint(integersBlock); - Value* const rightInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, rightValue); Value* const leftInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, leftValue); + Value* const rightInt = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, rightValue); Value* intResult = 0; // this will be an immediate operation result Value* intResultObject = 0; // this will be actual object to return @@ -1123,6 +1178,11 @@ void MethodCompiler::doSendBinary(TJITContext& jit) intResultObject->setName("bool."); } + if (encodeDirectIntegers) { + setNodeValue(jit, jit.currentNode, intResultObject); + return; + } + // Jumping out the integersBlock to the value aggregator jit.builder->CreateBr(resultBlock); @@ -1205,23 +1265,14 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& outs() << *directFunction << "\n"; } - // Allocating context object and temporaries on the methodFunction's stack. - // This operation does not affect garbage collector, so no pointer protection - // is required. Moreover, this is operation is much faster than heap allocation. - const bool hasTemporaries = context.getMethod()->temporarySize > 0; + // Allocating context object on the methodFunction's stack. This operation + // does not affect garbage collector, so no pointer protection is required. + // Moreover, this is operation is much faster than heap allocation. const uint32_t contextSize = sizeof(TContext); - const uint32_t tempsSize = hasTemporaries ? sizeof(TObjectArray) + sizeof(TObject*) * directMethod->temporarySize : 0; - - // Allocating stack space for objects and registering GC protection holder + // Allocating stack space for context and registering GC protection holder TStackObject contextPair = allocateStackObject(*jit.builder, sizeof(TContext), 0); Value* const contextSlot = contextPair.objectSlot; - Value* tempsSlot = 0; - - if (hasTemporaries) { - MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), directMethod->temporarySize); - tempsSlot = tempsPair.objectSlot; - } // Filling stack space with zeroes jit.builder->CreateMemSet( @@ -1232,20 +1283,9 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& false // volatile operation ); - if (hasTemporaries) { - jit.builder->CreateMemSet( - tempsSlot, // destination address - jit.builder->getInt8(0), // fill with zeroes - tempsSize, // size of object slot - 0, // no alignment - false // volatile operation - ); - } - // Initializing object fields // TODO Move the init sequence out of the block or check that it is correctly optimized in loops Value* const newContextObject = jit.builder->CreateBitCast(contextSlot, m_baseTypes.object->getPointerTo(), "newContext."); - Value* const newTempsObject = hasTemporaries ? jit.builder->CreateBitCast(tempsSlot, m_baseTypes.object->getPointerTo(), "newTemps.") : 0; Function* const setObjectSize = getBaseFunctions().setObjectSize; Function* const setObjectClass = getBaseFunctions().setObjectClass; @@ -1256,12 +1296,6 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& jit.builder->CreateCall2(setObjectSize, newContextObject, jit.builder->getInt32(contextFieldsCount)); jit.builder->CreateCall2(setObjectClass, newContextObject, getJitGlobals().contextClass); - if (hasTemporaries) { - const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; - jit.builder->CreateCall2(setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); - jit.builder->CreateCall2(setObjectClass, newTempsObject, getJitGlobals().arrayClass); - } - Function* const setObjectField = getBaseFunctions().setObjectField; Value* const methodRawPointer = jit.builder->getInt32(reinterpret_cast(directMethod)); Value* const directMethodObject = jit.builder->CreateIntToPtr(methodRawPointer, m_baseTypes.object->getPointerTo()); @@ -1274,19 +1308,16 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(0), directMethodObject); jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(1), messageArgumentsObject); - if (hasTemporaries) - jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(2), newTempsObject); - else - jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(2), getJitGlobals().nilObject); jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(3), contextObject); + // Note: temporaries will be allocated on the stack frame of the message handler Value* const newContext = jit.builder->CreateBitCast(newContextObject, m_baseTypes.context->getPointerTo()); Value* const result = jit.builder->CreateCall(directFunction, newContext); -// AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); -// Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); -// Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); -// jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); + AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); + Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); + Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); + jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); Value* const targetContext = jit.builder->CreateExtractValue(result, 1, "targetContext"); Value* const isBlockReturn = jit.builder->CreateIsNotNull(targetContext); From 3fc76593c503b16ed6dec7d29a38b4a454e50725 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 10 Jul 2016 17:43:52 +0600 Subject: [PATCH 083/105] Fixes shouldProtectProducer(), variable names and comments Issue: #17 Issue: #92 --- src/MethodCompiler.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index cbbdd34..a96f644 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -151,7 +151,7 @@ Value* MethodCompiler::protectPointer(TJITContext& jit, Value* value) Value* MethodCompiler::protectProducerNode(TJITContext& jit, st::ControlNode* node, Value* value) { - if (shouldProtectProducer(jit, jit.currentNode)) + if (shouldProtectProducer(jit, node)) return protectPointer(jit, value); else return value; // return value as is @@ -232,7 +232,7 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) bool MethodCompiler::shouldProtectProducer(TJITContext& jit, st::ControlNode* producer) { if (&jit.inferContext) { // FIXME Remove after block infer context is initialized - const type::Type& type = jit.inferContext[*jit.currentNode]; + const type::Type& type = jit.inferContext[*producer]; if (type.isLiteral() || type.getValue() == globals.smallIntClass) return false; } @@ -975,8 +975,8 @@ void MethodCompiler::doMarkArguments(TJITContext& jit) Value* const argumentsArray = jit.builder->CreateBitCast(argumentsObject, m_baseTypes.objectArray->getPointerTo()); Value* const argumentsPointer = jit.builder->CreateBitCast(argumentsObject, jit.builder->getInt8PtrTy()); - Function* gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_start); - jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->getInt64(sizeInBytes), argumentsPointer); + Function* lifetimeStartIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_start); + jit.builder->CreateCall2(lifetimeStartIntrinsic, jit.builder->getInt64(sizeInBytes), argumentsPointer); setNodeValue(jit, jit.currentNode, argumentsArray); } @@ -1190,6 +1190,7 @@ void MethodCompiler::doSendBinary(TJITContext& jit) jit.builder->SetInsertPoint(sendBinaryBlock); // We need to create an arguments array and fill it with argument objects // Then send the message just like ordinary one + // TODO direct call in case of inferred context // Now creating the argument array TObjectAndSize array = createArray(jit, 2); @@ -1308,16 +1309,16 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(0), directMethodObject); jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(1), messageArgumentsObject); + // Note: temporaries (2) will be allocated on the stack frame of the message handler jit.builder->CreateCall3(setObjectField, newContextObject, jit.builder->getInt32(3), contextObject); - // Note: temporaries will be allocated on the stack frame of the message handler Value* const newContext = jit.builder->CreateBitCast(newContextObject, m_baseTypes.context->getPointerTo()); Value* const result = jit.builder->CreateCall(directFunction, newContext); AllocaInst* const allocaInst = dyn_cast(jit.currentNode->getArgument()->getValue()->stripPointerCasts()); - Function* const gcrootIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); + Function* const lifetimeEndIntrinsic = getDeclaration(m_JITModule, Intrinsic::lifetime_end); Value* const argumentsPointer = jit.builder->CreateBitCast(arguments, jit.builder->getInt8PtrTy()); - jit.builder->CreateCall2(gcrootIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); + jit.builder->CreateCall2(lifetimeEndIntrinsic, jit.builder->CreateZExt(allocaInst->getArraySize(), jit.builder->getInt64Ty()), argumentsPointer); Value* const targetContext = jit.builder->CreateExtractValue(result, 1, "targetContext"); Value* const isBlockReturn = jit.builder->CreateIsNotNull(targetContext); From cc2e7369ef718275a6ffaaad8ced2c31d88ca89a Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 10:04:42 +0600 Subject: [PATCH 084/105] ControlGraph::TMetaInfo::readsArguments now stores indices of accessed args Issue: #17 Issue: #92 --- include/analysis.h | 2 +- src/ControlGraph.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/analysis.h b/include/analysis.h index b0d7a9a..8a7e0ca 100644 --- a/include/analysis.h +++ b/include/analysis.h @@ -524,7 +524,6 @@ class ControlGraph { bool usesSelf; bool usesSuper; - bool readsArguments; bool readsFields; bool writesFields; @@ -535,6 +534,7 @@ class ControlGraph { typedef std::vector TIndexList; TIndexList readsTemporaries; TIndexList writesTemporaries; + TIndexList readsArguments; static void insertIndex(std::size_t index, TIndexList& list) { if (std::find(list.begin(), list.end(), index) == list.end()) diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 85ecf78..5250735 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -14,7 +14,6 @@ ControlGraph::TMetaInfo::TMetaInfo() : hasBackEdgeTau(false), usesSelf(false), usesSuper(false), - readsArguments(false), readsFields(false), writesFields(false), hasPrimitive(false) @@ -190,7 +189,7 @@ void GraphConstructor::processNode(InstructionNode* node) case opcode::pushArgument: if (instruction.getArgument() == 0) m_graph->getMeta().usesSelf = true; - m_graph->getMeta().readsArguments = true; + ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsArguments); case opcode::pushInstance: m_graph->getMeta().readsFields = true; From f2c78e3752e3cc92b2dcca4d97313c7266bdb66f Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 10:05:42 +0600 Subject: [PATCH 085/105] Adds Type::isBlock() Issue: #17 Issue: #92 --- include/inference.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/inference.h b/include/inference.h index b274e41..eb62e33 100644 --- a/include/inference.h +++ b/include/inference.h @@ -76,6 +76,13 @@ class Type { bool isArray() const { return m_kind == tkArray; } bool isPolytype() const { return m_kind == tkPolytype; } + bool isBlock() const { + return + isMonotype() && + m_value == globals.blockClass && + !m_subTypes.empty(); + } + const TSubTypes& getSubTypes() const { return m_subTypes; } Type& pushSubType(const Type& type) { m_subTypes.push_back(type); return m_subTypes.back(); } From cf7d82269ed5ffb96d3a84d118a8361138493efc Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 10:14:35 +0600 Subject: [PATCH 086/105] Refactors JIT caches to support method/block specialization Issue: #17 Issue: #92 --- include/jit.h | 25 ++- src/JITRuntime.cpp | 377 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 332 insertions(+), 70 deletions(-) diff --git a/include/jit.h b/include/jit.h index b5a3fa4..1d1cf92 100644 --- a/include/jit.h +++ b/include/jit.h @@ -417,7 +417,7 @@ extern "C" { TObject* newOrdinaryObject(TClass* klass, uint32_t slotSize); TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize); TReturnValue sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex); - TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); TReturnValue invokeBlock(TBlock* block, TContext* callingContext); void emitBlockReturn(TObject* value, TContext* targetContext); const void* getBlockReturnType(); @@ -456,12 +456,12 @@ class JITRuntime { void sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex, TReturnValue& result); void invokeBlock(TBlock* block, TContext* callingContext, TReturnValue& result); - TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); friend TObject* newOrdinaryObject(TClass* klass, uint32_t slotSize); friend TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize); friend TReturnValue sendMessage(TContext* callingContext, TSymbol* message, TObjectArray* arguments, TClass* receiverClass, uint32_t callSiteIndex); - friend TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer); + friend TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop); friend TReturnValue invokeBlock(TBlock* block, TContext* callingContext); friend void emitBlockReturn(TObject* value, TContext* targetContext); @@ -478,11 +478,15 @@ class JITRuntime { { TMethod* containerMethod; uint32_t blockOffset; + TClass* closures[ARG_CACHE_SIZE * 2]; TBlockFunction function; }; static const unsigned int LOOKUP_CACHE_SIZE = 512; + static const unsigned int METHOD_CACHE_ASSOCIATIVITY = 4; + static const unsigned int BLOCK_CACHE_ASSOCIATIVITY = 4; + TFunctionCacheEntry m_functionLookupCache[LOOKUP_CACHE_SIZE]; TBlockFunctionCacheEntry m_blockFunctionLookupCache[LOOKUP_CACHE_SIZE]; uint32_t m_cacheHits; @@ -495,11 +499,22 @@ class JITRuntime { uint32_t m_objectsAllocated; TMethodFunction lookupFunctionInCache(TMethod* method, TObjectArray* arguments); - TBlockFunction lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset); + TBlockFunction lookupBlockFunctionInCache(TBlock* block); void updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments); - void updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function); + void updateBlockFunctionCache(TBlock* block, TBlockFunction function); void flushBlockFunctionCache(); + void fillMethodSlot( + TFunctionCacheEntry& entry, + TMethod* method, + TMethodFunction function, + TObjectArray* arguments); + + void fillBlockSlot( + TBlockFunctionCacheEntry& slot, + TBlock* block, + TBlockFunction function); + void initializePassManager(); //The following methods use m_baseTypes. Don't forget to init it before calling these methods diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 9c14baa..489289d 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -211,7 +211,7 @@ void JITRuntime::flushBlockFunctionCache() std::memset(&m_blockFunctionLookupCache, 0, sizeof(m_blockFunctionLookupCache)); } -TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer) +TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop) { hptr previousContext = m_softVM->newPointer(callingContext); @@ -224,6 +224,10 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u newBlock->arguments = previousContext->arguments; newBlock->temporaries = previousContext->temporaries; + // NOTE This field is used by JIT VM to aid specialization lookup + // See MethodCompiler::getClosureMask() and lookupBlockFunctionInCache() + newBlock->stackTop = stackTop; + // Assigning creatingContext depending on the hierarchy // Nested blocks inherit the outer creating context if (previousContext->getClass() == globals.blockClass) @@ -237,80 +241,333 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u JITRuntime::TMethodFunction JITRuntime::lookupFunctionInCache(TMethod* method, TObjectArray* arguments) { if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) { + // Current method has too many arguments that do not fit into the cache. + // It will never be cached, hence no need to search. m_cacheMisses++; return 0; } const uint32_t hash = (reinterpret_cast(method) << 2) + arguments->getSize(); - TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; - if (entry.method != method) { - m_cacheMisses++; - return 0; - } + // All method specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. - for (std::size_t i = 0; i < arguments->getSize(); i++) { - TClass* const cachedClass = entry.arguments[i]; - TObject* const field = arguments->getField(i); - TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + if (slot.method != method) + continue; + + bool match = true; + + // Checking that current entry matches the actual argument types + for (std::size_t argIndex = 0; argIndex < arguments->getSize(); argIndex++) { + TClass* const cachedClass = slot.arguments[argIndex]; + TObject* const field = arguments->getField(argIndex); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + // Current entry is from another specialization + match = false; + break; + } + } + + if (match) { + // Gotcha! We've just found an entry in the cache + // that has the same method and all passed arguments + // exactly match the stored types in the cache entry. - if (currentClass != cachedClass) { - m_cacheMisses++; - return 0; + // We've found the specialization that may be used. + m_cacheHits++; + return slot.function; } } - m_cacheHits++; - return entry.function; + // If all associated slots are visited, + // but no matching entry is found then + // it is definitely a cache miss. + + // Sad but true + m_cacheMisses++; + return 0; } -JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TMethod* containerMethod, uint32_t blockOffset) +void JITRuntime::fillMethodSlot( + JITRuntime::TFunctionCacheEntry& slot, + TMethod* method, + JITRuntime::TMethodFunction function, + TObjectArray* arguments) { - uint32_t hash = reinterpret_cast(containerMethod) ^ blockOffset; - TBlockFunctionCacheEntry& entry = m_blockFunctionLookupCache[hash % LOOKUP_CACHE_SIZE]; + slot.method = method; + slot.function = function; - if (entry.containerMethod == containerMethod && entry.blockOffset == blockOffset) { - m_blockCacheHits++; - return entry.function; - } else { - m_blockCacheMisses++; - return 0; + const std::size_t argSize = arguments->getSize(); + for (std::size_t i = 0; i < argSize; i++) { + TObject* const field = arguments->getField(i); + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.arguments[i] = currentClass; } + + // Class list should be zero terminated or filled completely + if (argSize < JITRuntime::ARG_CACHE_SIZE) + slot.arguments[argSize + 1] = 0; } void JITRuntime::updateFunctionCache(TMethod* method, TMethodFunction function, TObjectArray* arguments) { + // Check if we may cache the method at all. + // Some methods may have too many arguments + // that will not fit into the cache. if (!arguments || arguments->getSize() > ARG_CACHE_SIZE) return; - const std::size_t argSize = arguments->getSize(); - const uint32_t hash = (reinterpret_cast(method) << 2) + argSize; - TFunctionCacheEntry& entry = m_functionLookupCache[hash % LOOKUP_CACHE_SIZE]; + const uint32_t hash = (reinterpret_cast(method) << 2) + arguments->getSize(); - entry.method = method; - entry.function = function; + // All method specializations share the same hash. If we use 1-way associative cache + // then constant entry rotation will occur, as several specializations will compete + // for the single cache slot. It order to prevent this we use the n-way associative cache. - for (std::size_t i = 0; i < argSize; i++) { - TObject* const field = arguments->getField(i); - TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // We need to find a suitable slot preserving other friendly specializations if possible. + // If we'll find a slot occupied by a friendly specialization, then we'll try again. + if (slot.method == method) + continue; + + // We have found a slot that may be used + fillMethodSlot(slot, method, function, arguments); + return; + } + + // It seem that all slots are occupied by the friendly specializations. + // We need to chose the slot within bounds and overwrite it by our data. + + // Bit shift is required to eliminate pointer alignment or it would not work. + const uint32_t slotIndex = (reinterpret_cast(function) >> 4) % METHOD_CACHE_ASSOCIATIVITY; + + TFunctionCacheEntry& slot = m_functionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + fillMethodSlot(slot, method, function, arguments); +} + +JITRuntime::TBlockFunction JITRuntime::lookupBlockFunctionInCache(TBlock* block) +{ + // If closure mask is not defined then we could not perform fast lookup. + // This happens either when block came from the SoftVM or because block + // accesses too many temporaries and/or arguments. + if (! (block->stackTop.rawValue() & 1)) + return 0; + + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + + // The least significant bit is set to 1 to indicate that it is not an object pointer. + // It is not used in lookup logic, so we shift it out to make life easier: + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + + // All block specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. + + const uint32_t hash = (reinterpret_cast(containerMethod) << 16) ^ (blockOffset << 8) ^ closureMask; + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < METHOD_CACHE_ASSOCIATIVITY; slotIndex++) { + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // Quick check that discards most of the entries + if (slot.containerMethod != containerMethod || slot.blockOffset != blockOffset /*|| slot.closureMask != closureMask*/) + continue; // check the next slot + + if (! closureMask) { + // If no closure mask is defined then + // it is already a matching entry + + m_blockCacheHits++; + return slot.function; + } + + bool match = true; + + // If closure mask is defined, it means that block accesses temporaries and/or arguments + // In order to check whether current cached value match we check only the entries that + // correspond to a set bit. Others must be ignored because their value is undefined. + + // Masking out non-interesting bits, only temporaries will remain + if (uint32_t tempBits = closureMask & 0xFF) { + TObjectArray& temporaries = *block->temporaries; + + // Shift bits one by one and check for bits. + // Break if no more non-zero bits remain. + + for (std::size_t index = 0; tempBits; index++, tempBits >>= 1) { + // Check whether temporary corresponding to the current index is used by the block + const bool tempIsUsed = tempBits & 1; + + if (! tempIsUsed) + continue; // check the next temp - entry.arguments[i] = currentClass; + TClass* const cachedClass = slot.closures[index]; + TObject* const field = temporaries[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + match = false; + break; + } + } + } + + // Quick check for early return + if (! match) + continue; // check the next slot + + // Shifting and masking out non-interesting bits, only arguments will remain + if (uint32_t argBits = (closureMask >> 8) & 0xFF) { + TObjectArray& arguments = *block->arguments; + + for (std::size_t index = 0; argBits; index++, argBits >>= 1) { + // Check whether temporary corresponding to the current index is used by the block + const bool argIsUsed = argBits & 1; + + if (! argIsUsed) + continue; // check the next arg + + TClass* const cachedClass = slot.closures[index + 8]; + TObject* const field = arguments[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + if (currentClass != cachedClass) { + match = false; + break; + } + } + } + + if (match) { + // Gotcha! We've just found an entry in the cache + // that matched all passed arguments and temporaries. + + // We've found the specialization that may be used. + m_blockCacheHits++; + return slot.function; + } } - if (argSize < ARG_CACHE_SIZE) - entry.arguments[argSize + 1] = 0; + // If all associated slots are visited, + // but no matching entry is found then + // it is definitely a cache miss. + + // Sad but true + m_blockCacheMisses++; + return 0; } -void JITRuntime::updateBlockFunctionCache(TMethod* containerMethod, uint32_t blockOffset, TBlockFunction function) +void JITRuntime::fillBlockSlot( + JITRuntime::TBlockFunctionCacheEntry& slot, + TBlock* block, + JITRuntime::TBlockFunction function) { - uint32_t hash = reinterpret_cast(containerMethod) ^ blockOffset; - TBlockFunctionCacheEntry& entry = m_blockFunctionLookupCache[hash % LOOKUP_CACHE_SIZE]; + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + slot.containerMethod = containerMethod; + slot.blockOffset = blockOffset; + slot.function = function; + + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + if (! closureMask) + return; + + if (uint32_t tempBits = closureMask & 0xFF) { + TObjectArray& temporaries = *block->temporaries; + + for (std::size_t index = 0; tempBits; index++, tempBits >>= 1) { + const bool tempIsUsed = tempBits & 1; + + if (! tempIsUsed) + continue; + + TObject* const field = temporaries[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.closures[index] = currentClass; + } + } - entry.containerMethod = containerMethod; - entry.blockOffset = blockOffset; - entry.function = function; + if (uint32_t argBits = (closureMask >> 8) & 0xFF) { + TObjectArray& arguments = *block->arguments; + + for (std::size_t index = 0; argBits; index++, argBits >>= 1) { + const bool argIsUsed = argBits & 1; + + if (! argIsUsed) + continue; + + TObject* const field = arguments[index]; + TClass* const currentClass = isSmallInteger(field) ? globals.smallIntClass : field->getClass(); + + slot.closures[index + 8] = currentClass; + } + } } +void JITRuntime::updateBlockFunctionCache(TBlock* block, TBlockFunction function) +{ + // If closure mask is not defined then we could not cache the block. + // This happens either when block came from the SoftVM or because block + // accesses too many temporaries and/or arguments. + if (! (block->stackTop.rawValue() & 1)) + return; + + const uint32_t blockOffset = block->blockBytePointer; + TMethod* const containerMethod = block->method; + + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + + // The least significant bit is set to 1 to indicate that it is not an object pointer. + // It is not used in lookup logic, so we shift it out to make life easier: + const uint32_t closureMask = static_cast(block->stackTop.rawValue()) >> 1; + + // All block specializations share the same hash. If cache would be 1-way associative + // then constant cache rotation will occur as several specializations will compete + // against the single cache slot. It order to prevent this we use the n-way cache. + + const uint32_t hash = (reinterpret_cast(containerMethod) << 16) ^ (blockOffset << 8) ^ closureMask; + + // Outer loop iterates over the associated slots + for (std::size_t slotIndex = 0; slotIndex < BLOCK_CACHE_ASSOCIATIVITY; slotIndex++) { + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + + // We need to find a suitable slot preserving other friendly specializations if possible. + // If we'll find a slot occupied by a friendly specialization, then we'll try again. + if (slot.containerMethod == containerMethod && slot.blockOffset == blockOffset) + continue; // check the next slot + + // We have found a slot that may be used + fillBlockSlot(slot, block, function); + } + + // It seem that all slots are occupied by the friendly specializations. + // We need to chose the slot within bounds and overwrite it by our data. + + // Bit shift is required to eliminate pointer alignment or it would not work. + const uint32_t slotIndex = (reinterpret_cast(function) >> 4) % METHOD_CACHE_ASSOCIATIVITY; + + TBlockFunctionCacheEntry& slot = m_blockFunctionLookupCache[(hash + slotIndex) % LOOKUP_CACHE_SIZE]; + fillBlockSlot(slot, block, function); +} void JITRuntime::optimizeFunction(Function* function, bool runModulePass) { @@ -325,40 +582,30 @@ void JITRuntime::invokeBlock(TBlock* block, TContext* callingContext, TReturnVal { m_blocksInvoked++; - // Guessing the block function name - const uint16_t blockOffset = block->blockBytePointer; - - TBlockFunction compiledBlockFunction = once ? 0 : lookupBlockFunctionInCache(block->method, blockOffset); + TBlockFunction compiledBlockFunction = once ? 0 : lookupBlockFunctionInCache(block); Function* blockFunction = 0; if (! compiledBlockFunction) { - std::ostringstream ss; - ss << block->method->klass->name->toString() << ">>" << block->method->name->toString() << "@" << blockOffset; - std::string blockFunctionName = ss.str(); - - blockFunction = m_JITModule->getFunction(blockFunctionName); - if (!blockFunction) { - // Block functions are created when wrapping method gets compiled. - // If function was not found then the whole method needs compilation. + // Block functions are created when wrapping method gets compiled. + // If function was not found then the whole method needs compilation. - // Compiling function and storing it to the table for further use - blockFunction = m_methodCompiler->compileBlock(block); + // Compiling function and storing it to the table for further use + blockFunction = m_methodCompiler->compileBlock(block); - if (!blockFunction) { - // Something is really wrong! - outs() << "JIT: Fatal error in invokeBlock for " << blockFunctionName << "\n"; - std::exit(1); - } + if (!blockFunction) { + // Something is really wrong! + outs() << "JIT: Fatal error in invokeBlock\n"; + std::exit(1); + } // outs() << *blockFunction << "\n"; verifyModule(*m_JITModule, AbortProcessAction); - optimizeFunction(blockFunction, true); - } + optimizeFunction(blockFunction, true); // FIXME compiledBlockFunction = reinterpret_cast(m_executionEngine->getPointerToFunction(blockFunction)); - updateBlockFunctionCache(block->method, blockOffset, compiledBlockFunction); + updateBlockFunctionCache(block, compiledBlockFunction); } block->previousContext = callingContext->previousContext; @@ -1134,9 +1381,9 @@ TByteObject* newBinaryObject(TClass* klass, uint32_t dataSize) return JITRuntime::Instance()->getVM()->newBinaryObject(klass, dataSize); } -TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer) +TBlock* createBlock(TContext* callingContext, uint8_t argLocation, uint16_t bytePointer, uint32_t stackTop) { - return JITRuntime::Instance()->createBlock(callingContext, argLocation, bytePointer); + return JITRuntime::Instance()->createBlock(callingContext, argLocation, bytePointer, stackTop); } void emitBlockReturn(TObject* value, TContext* targetContext) From 9d391820566b236b9b56185815c3334dc3a065a6 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 10:21:12 +0600 Subject: [PATCH 087/105] Adds support for direct calls of inferred blocks Issue: #17 Issue: #92 --- include/jit.h | 18 ++- src/MethodCompiler.cpp | 358 +++++++++++++++++++++++++++++------------ 2 files changed, 264 insertions(+), 112 deletions(-) diff --git a/include/jit.h b/include/jit.h index 1d1cf92..10e17b1 100644 --- a/include/jit.h +++ b/include/jit.h @@ -198,6 +198,8 @@ class MethodCompiler { }; struct TJITContext { + bool isBlock; + st::ParsedMethod* parsedMethod; st::ControlGraph* controlGraph; st::InstructionNode* currentNode; @@ -237,6 +239,7 @@ class MethodCompiler { type::InferContext& context, bool parse = true ) : + isBlock(false), currentNode(0), originMethod(method), inferContext(context), @@ -272,14 +275,16 @@ class MethodCompiler { TJITBlockContext( MethodCompiler* compiler, st::ParsedMethod* method, - st::ParsedBlock* block - ) - : TJITContext(compiler, 0, *(type::InferContext*)(0), false), parsedBlock(block) + st::ParsedBlock* block, + st::ControlGraph* blockGraph, + type::InferContext& context + ) : + TJITContext(compiler, 0, context, false), + parsedBlock(block) { parsedMethod = method; originMethod = parsedMethod->getOrigin(); - controlGraph = new st::ControlGraph(method, block); - controlGraph->buildGraph(); + controlGraph = blockGraph; } ~TJITBlockContext() { @@ -332,7 +337,8 @@ class MethodCompiler { void doPushConstant(TJITContext& jit); void doPushBlock(TJITContext& jit); - llvm::Function* compileBlock(TJITContext& jit, const std::string& blockFunctionName, st::ParsedBlock* parsedBlock); + llvm::Function* compileBlock(const std::string& blockFunctionName, st::ParsedBlock* parsedBlock, type::InferContext& blockContext); + llvm::Function* compileInvokedBlock(TJITContext& jit); void doAssignTemporary(TJITContext& jit); void doAssignInstance(TJITContext& jit); diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index a96f644..b9d60bc 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -231,11 +231,9 @@ bool MethodCompiler::methodAllocatesMemory(TJITContext& jit) bool MethodCompiler::shouldProtectProducer(TJITContext& jit, st::ControlNode* producer) { - if (&jit.inferContext) { // FIXME Remove after block infer context is initialized - const type::Type& type = jit.inferContext[*producer]; - if (type.isLiteral() || type.getValue() == globals.smallIntClass) - return false; - } + const type::Type& type = jit.inferContext[*producer]; + if (type.isLiteral() || type.getValue() == globals.smallIntClass) + return false; // We should protect the value by holder if consumer of this value is far away. // By far away we mean that it crosses the barrier of potential garbage collection. @@ -318,7 +316,8 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) context = jit.builder->CreateBitCast(blockContext, m_baseTypes.context->getPointerTo()); } - jit.contextHolder = context; + // FIXME Temporary until block context is stack allocated + jit.contextHolder = jit.methodAllocatesMemory ? protectPointer(jit, context) : context; // Storing self pointer Value* const pargs = jit.builder->CreateStructGEP(context, 2); @@ -335,35 +334,46 @@ void MethodCompiler::writePreamble(TJITContext& jit, bool isBlock) const std::size_t tempsCount = jit.originMethod->temporarySize; if (tempsCount) { - const uint32_t tempsSize = sizeof(TObjectArray) + sizeof(TObject*) * tempsCount; + if (isBlock) { + // TODO Protect pointer + Value* const ptemps = jit.builder->CreateStructGEP(context, 3); + Value* const temporaries = jit.builder->CreateLoad(ptemps); + jit.temporaries = jit.builder->CreateBitCast(temporaries, m_baseTypes.object->getPointerTo()); + } else { + const uint32_t tempsSize = sizeof(TObjectArray) + sizeof(TObject*) * tempsCount; - MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), tempsCount); + MethodCompiler::TStackObject tempsPair = allocateStackObject(*jit.builder, sizeof(TObjectArray), tempsCount); - jit.builder->CreateMemSet( - tempsPair.objectSlot, // destination address - jit.builder->getInt8(0), // fill with zeroes - tempsSize, // size of object slot - 0, // no alignment - false // volatile operation - ); + jit.builder->CreateMemSet( + tempsPair.objectSlot, // destination address + jit.builder->getInt8(0), // fill with zeroes + tempsSize, // size of object slot + 0, // no alignment + false // volatile operation + ); - Value* const newTempsObject = jit.builder->CreateBitCast(tempsPair.objectSlot, m_baseTypes.object->getPointerTo(), "temps."); + Value* const newTempsObject = jit.builder->CreateBitCast(tempsPair.objectSlot, m_baseTypes.object->getPointerTo(), "temps."); - const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; - jit.builder->CreateCall2(getBaseFunctions().setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); - jit.builder->CreateCall2(getBaseFunctions().setObjectClass, newTempsObject, getJitGlobals().arrayClass); + const uint32_t tempsFieldsCount = tempsSize / sizeof(TObject*) - 2; + jit.builder->CreateCall2(getBaseFunctions().setObjectSize, newTempsObject, jit.builder->getInt32(tempsFieldsCount)); + jit.builder->CreateCall2(getBaseFunctions().setObjectClass, newTempsObject, getJitGlobals().arrayClass); - Value* const contextObject = jit.builder->CreateBitCast(context, m_baseTypes.object->getPointerTo()); - jit.builder->CreateCall3(getBaseFunctions().setObjectField, contextObject, jit.builder->getInt32(2), newTempsObject); + Value* const contextObject = jit.builder->CreateBitCast(context, m_baseTypes.object->getPointerTo()); + jit.builder->CreateCall3(getBaseFunctions().setObjectField, contextObject, jit.builder->getInt32(2), newTempsObject); - jit.temporaries = newTempsObject; + jit.temporaries = newTempsObject; + } } } Value* MethodCompiler::TJITContext::getCurrentContext() { - return contextHolder; + // FIXME Temporary until block context is stack allocated + if (methodAllocatesMemory) + return builder->CreateLoad(contextHolder, "self."); + else + return contextHolder; } Value* MethodCompiler::TJITContext::getSelf() @@ -733,12 +743,24 @@ void MethodCompiler::doPushArgument(TJITContext& jit) void MethodCompiler::doPushTemporary(TJITContext& jit) { const uint8_t index = jit.currentNode->getInstruction().getArgument(); - Value* const temporary = jit.builder->CreateCall2( - m_baseFunctions.getObjectField, - jit.temporaries, - jit.builder->getInt32(index) - ); + Value* temporary = 0; + + if (jit.isBlock) { + temporary = jit.builder->CreateCall2( + m_baseFunctions.getTemporary, + jit.getCurrentContext(), + jit.builder->getInt32(index) + ); + } else { + temporary = jit.builder->CreateCall2( + m_baseFunctions.getObjectField, + jit.temporaries, + jit.builder->getInt32(index) + ); + } + + assert(temporary); temporary->setName(std::string("temp") + to_string(index) + "."); Value* const holder = protectProducerNode(jit, jit.currentNode, temporary); @@ -805,25 +827,59 @@ void MethodCompiler::doPushConstant(TJITContext& jit) setNodeValue(jit, jit.currentNode, constantValue); } +uint32_t getClosureMask(st::ControlGraph& blockGraph) +{ + // Closure mask is a bit mask representing indices of the temporaries (T) + // and arguments (A) that are accessed from within a block. + // Currently only 17 bits are defined: xxxxxxxxxxxxxxx|AAAAAAAA|TTTTTTTT|1 + // The least significant bit is set to 1 to indicate that it is not an object pointer. + + std::bitset<32> closureMask; + + closureMask[0] = 1; + + const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + const uint32_t tempIndex = readsTemporaries[index]; + + if (tempIndex < 8) + closureMask[tempIndex + 1] = 1; + else + return 0; // method could not be cashed + } + + const st::ControlGraph::TMetaInfo::TIndexList& readsArguments = blockGraph.getMeta().readsArguments; + for (std::size_t index = 0; index < readsArguments.size(); index++) { + const uint32_t argIndex = readsArguments[index]; + + if (argIndex < 8) + closureMask[argIndex + 8 + 1] = 1; + else + return 0; // method could not be cashed + } + + return closureMask.to_ulong(); +} + void MethodCompiler::doPushBlock(TJITContext& jit) { + // TODO Check that this block is actually used/invoked and not escapes. + st::PushBlockNode* const pushBlockNode = jit.currentNode->cast(); st::ParsedBlock* const parsedBlock = pushBlockNode->getParsedBlock(); const uint16_t blockOffset = parsedBlock->getStartOffset(); - std::ostringstream ss; - ss << jit.originMethod->klass->name->toString() + ">>" + jit.originMethod->name->toString() << "@" << blockOffset; - std::string blockFunctionName = ss.str(); + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); - // If block function is not already created, create it - if (! m_JITModule->getFunction(blockFunctionName)) - compileBlock(jit, blockFunctionName, parsedBlock); + const uint32_t argumentLocation = jit.currentNode->getInstruction().getArgument(); + const uint32_t closureMask = getClosureMask(*blockGraph); // Create block object and fill it with context information Value* const args[] = { - jit.getCurrentContext(), // creatingContext - jit.builder->getInt8(jit.currentNode->getInstruction().getArgument()), // arg offset - jit.builder->getInt16(blockOffset) + jit.getCurrentContext(), // creatingContext + jit.builder->getInt8(argumentLocation), // arg offset + jit.builder->getInt16(blockOffset), // block byte pointer + jit.builder->getInt32(closureMask) // closure mask as stack top }; Value* blockObject = jit.builder->CreateCall(m_runtimeAPI.createBlock, args); @@ -834,30 +890,116 @@ void MethodCompiler::doPushBlock(TJITContext& jit) setNodeValue(jit, jit.currentNode, blockHolder); } +void createBlockTypes( + TBlock* block, + st::ControlGraph& blockGraph, + type::Type& blockType, + type::Type& blockArguments, + type::InferContext::TVariableMap& closureTypes) +{ + blockType.set(globals.blockClass, type::Type::tkMonotype); + blockType.pushSubType(block->method); // [Type::bstOrigin] + blockType.pushSubType(type::Type(TInteger(block->blockBytePointer))); // [Type::bstOffset] + blockType.pushSubType(type::Type(TInteger(block->argumentLocation))); // [Type::bstArgIndex] + blockType.pushSubType(type::Type(TInteger(0))); // [Type::bstContextIndex] + + blockArguments.set(globals.arrayClass, type::Type::tkArray); + + TObjectArray& temporaries = * block->creatingContext->temporaries; + const uint32_t argumentLocation = block->argumentLocation; + + const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { + const uint32_t tempIndex = readsTemporaries[index]; + TObject* const argument = temporaries[tempIndex]; + TClass* const klass = isSmallInteger(argument) ? globals.smallIntClass : argument->getClass(); + const type::Type newType(klass); + + if (readsTemporaries[index] < argumentLocation) + closureTypes[tempIndex] = newType; + else + blockArguments.pushSubType(newType); + } + +} + +std::string getBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments) +{ + return blockArguments.toString() + "::" + blockType.toString(); +} + llvm::Function* MethodCompiler::compileBlock(TBlock* block) { - // TODO + const uint16_t blockOffset = block->blockBytePointer; + st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(block->method); + st::ParsedBlock* const parsedBlock = methodGraph->getParsedMethod()->getParsedBlockByOffset(blockOffset); + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); + + const type::Type& methodArguments = type::createArgumentsType(block->arguments); + + type::InferContext methodContext(block->method, 0, methodArguments); + type::InferContext::TVariableMap& closureTypes = methodContext.getBlockClosures()[0]; + type::Type blockType; - type::Type arguments; - type::InferContext* const inferContext = m_typeSystem.inferBlock(blockType, arguments, 0); + type::Type blockArguments(globals.arrayClass, type::Type::tkArray); + createBlockTypes(block, *blockGraph, blockType, blockArguments, closureTypes); - TJITContext blockContext(this, block->method, *inferContext); - const uint16_t blockOffset = block->blockBytePointer; + // Check if function was already compiled + const std::string& blockFunctionName = getBlockFunctionName(blockType, blockArguments); + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; - std::ostringstream ss; - ss << block->method->klass->name->toString() << ">>" << block->method->name->toString() << "@" << blockOffset; - const std::string blockFunctionName = ss.str(); + type::TContextStack stack(methodContext); + type::InferContext* const blockInferContext = m_typeSystem.inferBlock(blockType, blockArguments, &stack); + assert(blockInferContext); + + return compileBlock(blockFunctionName, parsedBlock, *blockInferContext); +} + +llvm::Function* MethodCompiler::compileInvokedBlock(TJITContext& jit) +{ + type::Type& blockType = jit.inferContext[*jit.currentNode->getArgument()]; + if (blockType.isBlock()) + return 0; + + type::Type blockArguments(type::Type::tkArray); - st::ParsedBlock* const parsedBlock = blockContext.parsedMethod->getParsedBlockByOffset(blockOffset); - assert(parsedBlock); + if (jit.currentNode->getArgumentsCount() > 1) + blockArguments.pushSubType(jit.inferContext[*jit.currentNode->getArgument(1)]); - return compileBlock(blockContext, blockFunctionName, parsedBlock); + if (jit.currentNode->getArgumentsCount() > 2) + blockArguments.pushSubType(jit.inferContext[*jit.currentNode->getArgument(2)]); + + type::TContextStack stack(jit.inferContext); // Probably not needed, the context must already be cached + type::InferContext* const blockContext = m_typeSystem.inferBlock(blockType, blockArguments, &stack); + + if (! blockContext) + return 0; + + const std::string& blockFunctionName = getBlockFunctionName(blockType, blockArguments); + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; + + TMethod* const method = blockType[type::Type::bstOrigin].getValue()->cast(); + const uint16_t blockOffset = TInteger(blockType[type::Type::bstOffset].getValue()); + + st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(method); + st::ParsedBlock* const parsedBlock = methodGraph->getParsedMethod()->getParsedBlockByOffset(blockOffset); + + return compileBlock(blockFunctionName, parsedBlock, *blockContext); } -llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string& blockFunctionName, st::ParsedBlock* parsedBlock) +llvm::Function* MethodCompiler::compileBlock(const std::string& blockFunctionName, st::ParsedBlock* parsedBlock, type::InferContext& blockcontext) { - const uint16_t blockOffset = parsedBlock->getStartOffset(); - TJITBlockContext blockContext(this, jit.parsedMethod, parsedBlock); + // Check if function was already compiled + if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) + return blockFunction; + + printf("compiling block %s\n", blockFunctionName.c_str()); + + st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); + + TJITBlockContext blockContext(this, parsedBlock->getContainer(), parsedBlock, blockGraph, blockcontext); { ControlGraphVisualizer vis(blockContext.controlGraph, blockFunctionName, "dots/"); @@ -873,59 +1015,58 @@ llvm::Function* MethodCompiler::compileBlock(TJITContext& jit, const std::string false // we're not dealing with vararg ); - blockContext.function = m_JITModule->getFunction(blockFunctionName); - if (! blockContext.function) { // Checking if not already created - blockContext.function = cast(m_JITModule->getOrInsertFunction(blockFunctionName, blockFunctionType)); + blockContext.function = cast(m_JITModule->getOrInsertFunction(blockFunctionName, blockFunctionType)); - blockContext.function->setGC("shadow-stack"); - m_blockFunctions[blockFunctionName] = blockContext.function; + blockContext.function->setGC("shadow-stack"); + m_blockFunctions[blockFunctionName] = blockContext.function; - // Creating the basic block and inserting it into the function - blockContext.preamble = BasicBlock::Create(m_JITModule->getContext(), "blockPreamble", blockContext.function); - blockContext.builder = new IRBuilder<>(blockContext.preamble); - writePreamble(blockContext, /*isBlock*/ true); + // Creating the basic block and inserting it into the function + blockContext.preamble = BasicBlock::Create(m_JITModule->getContext(), "blockPreamble", blockContext.function); + blockContext.builder = new IRBuilder<>(blockContext.preamble); + writePreamble(blockContext, /*isBlock*/ true); - writeUnwindBlockReturn(blockContext); + writeUnwindBlockReturn(blockContext); - scanForBranches(blockContext, blockContext.parsedBlock); + scanForBranches(blockContext, blockContext.parsedBlock); - std::stringstream ss; - ss.str(""); - ss << "offset" << blockOffset; + const uint16_t blockOffset = parsedBlock->getStartOffset(); - BasicBlock* const blockBody = parsedBlock->getBasicBlockByOffset(blockOffset)->getValue(); // m_targetToBlockMap[blockOffset]; - assert(blockBody); - blockBody->setName(ss.str()); + std::stringstream ss; + ss.str(""); + ss << "offset" << blockOffset; - blockContext.builder->SetInsertPoint(blockContext.preamble); - blockContext.builder->CreateBr(blockBody); + BasicBlock* const blockBody = parsedBlock->getBasicBlockByOffset(blockOffset)->getValue(); // m_targetToBlockMap[blockOffset]; + assert(blockBody); + blockBody->setName(ss.str()); - blockContext.builder->SetInsertPoint(blockBody); + blockContext.builder->SetInsertPoint(blockContext.preamble); + blockContext.builder->CreateBr(blockBody); - writeFunctionBody(blockContext); + blockContext.builder->SetInsertPoint(blockBody); - // Erasing unwind phi if it is not needed - // Block will be removed by DCE - // TODO Better to detect it before unwind block is written - if (! blockContext.unwindBlockReturn->hasNUsesOrMore(1)) { - if (blockContext.unwindPhi->getNumIncomingValues()) { - outs() << *blockContext.function << "\n"; - outs() << "Fatal error: phi is used but the block isn't!\n"; - abort(); - } + writeFunctionBody(blockContext); - blockContext.unwindPhi->replaceAllUsesWith(UndefValue::get(m_baseTypes.returnValueType)); - blockContext.unwindPhi->eraseFromParent(); - blockContext.unwindPhi = 0; + // Erasing unwind phi if it is not needed + // Block will be removed by DCE + // TODO Better to detect it before unwind block is written + if (! blockContext.unwindBlockReturn->hasNUsesOrMore(1)) { + if (blockContext.unwindPhi->getNumIncomingValues()) { + outs() << *blockContext.function << "\n"; + outs() << "Fatal error: phi is used but the block isn't!\n"; + abort(); } - // outs() << *blockContext.function << "\n"; + blockContext.unwindPhi->replaceAllUsesWith(UndefValue::get(m_baseTypes.returnValueType)); + blockContext.unwindPhi->eraseFromParent(); + blockContext.unwindPhi = 0; + } + + outs() << *blockContext.function << "\n"; - verifyFunction(*blockContext.function); + verifyFunction(*blockContext.function); - // Running optimization passes on a block function - JITRuntime::Instance()->optimizeFunction(blockContext.function, false); - } + // Running optimization passes on a block function + //JITRuntime::Instance()->optimizeFunction(blockContext.function, false); return blockContext.function; } @@ -935,12 +1076,21 @@ void MethodCompiler::doAssignTemporary(TJITContext& jit) const uint8_t index = jit.currentNode->getInstruction().getArgument(); Value* const value = getArgument(jit); - jit.builder->CreateCall3( - m_baseFunctions.setObjectField, - jit.temporaries, - jit.builder->getInt32(index), - value - ); + if (jit.isBlock) { + jit.builder->CreateCall3( + m_baseFunctions.setTemporary, + jit.getCurrentContext(), + jit.builder->getInt32(index), + value + ); + } else { + jit.builder->CreateCall3( + m_baseFunctions.setObjectField, + jit.temporaries, + jit.builder->getInt32(index), + value + ); + } } void MethodCompiler::doAssignInstance(TJITContext& jit) @@ -1335,13 +1485,6 @@ void MethodCompiler::doSendInferredMessage(TJITContext& jit, type::InferContext& void MethodCompiler::doSendMessage(TJITContext& jit) { - // FIXME Temporary hack until block inference - // is not handled in method compiler - if (! &jit.inferContext) { - doSendGenericMessage(jit); - return; - } - const type::Type& arguments = jit.inferContext[*jit.currentNode->getArgument()]; if (arguments.isArray() && !arguments.getSubTypes().empty()) { @@ -1757,6 +1900,7 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, BasicBlock* const tempsChecked = BasicBlock::Create(m_JITModule->getContext(), "tempsChecked.", jit.function); tempsChecked->moveAfter(jit.builder->GetInsertBlock()); + // TODO Remove the check if block is inferred //Checking the passed temps size TODO unroll stack Value* const blockAcceptsArgCount = jit.builder->CreateSub(tempsSize, argumentLocation, "blockAcceptsArgCount."); Value* const tempSizeOk = jit.builder->CreateICmpSLE(jit.builder->getInt32(argCount), blockAcceptsArgCount, "tempSizeOk."); @@ -1773,10 +1917,12 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, jit.builder->CreateCall3(m_baseFunctions.setObjectField, blockTemps, fieldIndex, argument); } - Value* const args[] = { block, jit.getCurrentContext() }; - Value* const result = jit.builder->CreateCall(m_runtimeAPI.invokeBlock, args); - - primitiveResult = result; + if (Function* const blockFunction = compileInvokedBlock(jit)) + // Direct call to the inferred block function + primitiveResult = jit.builder->CreateCall(blockFunction, block); + else + // Generic dispatch using runtime + primitiveResult = jit.builder->CreateCall2(m_runtimeAPI.invokeBlock, block, jit.getCurrentContext()); } break; case primitive::throwError: { //19 From 5263fe0b7a4b9b3490c10256faf45895a075f125 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 23:45:54 +0600 Subject: [PATCH 088/105] Adds nounwind specifier to core helper functions Issue: #17 Issue: #92 --- include/Core.ll | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/include/Core.ll b/include/Core.ll index e4036d8..66fc1c0 100644 --- a/include/Core.ll +++ b/include/Core.ll @@ -153,7 +153,7 @@ ;;;;;;;;;;;;;;;;;;;; functions ;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -define i1 @isSmallInteger(%TObject* %value) alwaysinline { +define i1 @isSmallInteger(%TObject* %value) alwaysinline nounwind { ; return reinterpret_cast(value) & 1; %int = ptrtoint %TObject* %value to i32 @@ -161,7 +161,7 @@ define i1 @isSmallInteger(%TObject* %value) alwaysinline { ret i1 %result } -define i32 @getIntegerValue(%TObject* %value) alwaysinline { +define i32 @getIntegerValue(%TObject* %value) alwaysinline nounwind { ; return (int32_t) (value >> 1) %int = ptrtoint %TObject* %value to i32 @@ -169,7 +169,7 @@ define i32 @getIntegerValue(%TObject* %value) alwaysinline { ret i32 %result } -define %TObject* @newInteger(i32 %value) alwaysinline { +define %TObject* @newInteger(i32 %value) alwaysinline nounwind { ; return reinterpret_cast( (value << 1) | 1 ); %shled = shl i32 %value, 1 @@ -178,7 +178,7 @@ define %TObject* @newInteger(i32 %value) alwaysinline { ret %TObject* %result } -define i32 @getSlotSize(i32 %fieldsCount) alwaysinline { +define i32 @getSlotSize(i32 %fieldsCount) alwaysinline nounwind { ;sizeof(TObject) + fieldsCount * sizeof(TObject*) %fieldsSize = mul i32 4, %fieldsCount @@ -188,21 +188,21 @@ define i32 @getSlotSize(i32 %fieldsCount) alwaysinline { } -define i32 @getObjectSize(%TObject* %this) alwaysinline { +define i32 @getObjectSize(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %result = lshr i32 %data, 2 ret i32 %result } -define %TObject* @setObjectSize(%TObject* %this, i32 %size) alwaysinline { +define %TObject* @setObjectSize(%TObject* %this, i32 %size) alwaysinline nounwind { %addr = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %ssize = shl i32 %size, 2 store i32 %ssize, i32* %addr ret %TObject* %this } -define i1 @isObjectRelocated(%TObject* %this) alwaysinline { +define i1 @isObjectRelocated(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %field = and i32 %data, 1 @@ -210,7 +210,7 @@ define i1 @isObjectRelocated(%TObject* %this) alwaysinline { ret i1 %result } -define i1 @isObjectBinary(%TObject* %this) alwaysinline { +define i1 @isObjectBinary(%TObject* %this) alwaysinline nounwind { %1 = getelementptr %TObject* %this, i32 0, i32 0, i32 0 %data = load i32* %1 %field = and i32 %data, 2 @@ -218,14 +218,14 @@ define i1 @isObjectBinary(%TObject* %this) alwaysinline { ret i1 %result } -define %TClass** @getObjectClassPtr(%TObject* %this) alwaysinline { +define %TClass** @getObjectClassPtr(%TObject* %this) alwaysinline nounwind { %pclass = getelementptr inbounds %TObject* %this, i32 0, i32 1 ret %TClass** %pclass } @SmallInt = external global %TClass -define %TClass* @getObjectClass(%TObject* %this) alwaysinline { +define %TClass* @getObjectClass(%TObject* %this) alwaysinline nounwind { ; TODO SmallInt case %test = call i1 @isSmallInteger(%TObject* %this) br i1 %test, label %is_smallint, label %is_object @@ -237,36 +237,36 @@ is_object: ret %TClass* %class } -define %TObject* @setObjectClass(%TObject* %this, %TClass* %class) alwaysinline { +define %TObject* @setObjectClass(%TObject* %this, %TClass* %class) alwaysinline nounwind { %addr = call %TClass** @getObjectClassPtr(%TObject* %this) store %TClass* %class, %TClass** %addr ret %TObject* %this } -define %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) alwaysinline { +define %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) alwaysinline nounwind { %fields = getelementptr inbounds %TObject* %object, i32 0, i32 2 %fieldPtr = getelementptr inbounds [0 x %TObject*]* %fields, i32 0, i32 %index ret %TObject** %fieldPtr } -define %TObject** @getObjectFields(%TObject* %this) alwaysinline { +define %TObject** @getObjectFields(%TObject* %this) alwaysinline nounwind { %fieldsPtr = call %TObject** @getObjectFieldPtr(%TObject* %this, i32 0) ret %TObject** %fieldsPtr } -define %TObject* @getObjectField(%TObject* %object, i32 %index) alwaysinline { +define %TObject* @getObjectField(%TObject* %object, i32 %index) alwaysinline nounwind { %fieldPtr = call %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) %result = load %TObject** %fieldPtr ret %TObject* %result } -define %TObject** @setObjectField(%TObject* %object, i32 %index, %TObject* %value) alwaysinline { +define %TObject** @setObjectField(%TObject* %object, i32 %index, %TObject* %value) alwaysinline nounwind { %fieldPtr = call %TObject** @getObjectFieldPtr(%TObject* %object, i32 %index) store %TObject* %value, %TObject** %fieldPtr ret %TObject** %fieldPtr } -define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %argsPtr = getelementptr inbounds %TContext* %context, i32 0, i32 2 %args = load %TObjectArray** %argsPtr %argsObj = bitcast %TObjectArray* %args to %TObject* @@ -274,7 +274,7 @@ define %TObject* @getArgFromContext(%TContext* %context, i32 %index) alwaysinlin ret %TObject* %arg } -define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %methodPtr = getelementptr inbounds %TContext* %context, i32 0, i32 1 %method = load %TMethod** %methodPtr %literalsPtr = getelementptr inbounds %TMethod* %method, i32 0, i32 3 @@ -284,32 +284,32 @@ define %TObject* @getLiteralFromContext(%TContext* %context, i32 %index) alwaysi ret %TObject* %literal } -define %TObject* @getTempsFromContext(%TContext* %context) alwaysinline { +define %TObject* @getTempsFromContext(%TContext* %context) alwaysinline nounwind { %tempsPtr = getelementptr inbounds %TContext* %context, i32 0, i32 3 %temps = load %TObjectArray** %tempsPtr %tempsObj = bitcast %TObjectArray* %temps to %TObject* ret %TObject* %tempsObj } -define %TObject* @getTemporaryFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getTemporaryFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %temps = call %TObject* @getTempsFromContext(%TContext* %context) %temporary = call %TObject* @getObjectField(%TObject* %temps, i32 %index) ret %TObject* %temporary } -define void @setTemporaryInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline { +define void @setTemporaryInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline nounwind { %temps = call %TObject* @getTempsFromContext(%TContext* %context) call %TObject** @setObjectField(%TObject* %temps, i32 %index, %TObject* %value) ret void } -define %TObject* @getInstanceFromContext(%TContext* %context, i32 %index) alwaysinline { +define %TObject* @getInstanceFromContext(%TContext* %context, i32 %index) alwaysinline nounwind { %self = call %TObject* @getArgFromContext(%TContext* %context, i32 0) %instance = call %TObject* @getObjectField(%TObject* %self, i32 %index) ret %TObject* %instance } -define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline { +define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %value) alwaysinline nounwind { %self = call %TObject* @getArgFromContext(%TContext* %context, i32 0) %instancePtr = call %TObject** @getObjectFieldPtr(%TObject* %self, i32 %index) call void @checkRoot(%TObject* %value, %TObject** %instancePtr) @@ -317,7 +317,7 @@ define void @setInstanceInContext(%TContext* %context, i32 %index, %TObject* %va ret void } -define void @dummy() gc "shadow-stack" { +define void @dummy() nounwind gc "shadow-stack" { ; enabling shadow stack init on this module ret void } @@ -336,7 +336,7 @@ declare %TByteObject* @newBinaryObject(%TClass*, i32) ;declare %TObject* @sendMessage(%TContext* %callingContext, %TSymbol* %selector, %TObjectArray* %arguments, %TClass* %receiverClass, i32 %callSiteOffset) declare { %TObject*, %TContext* } @sendMessage(%TContext* %callingContext, %TSymbol* %selector, %TObjectArray* %arguments, %TClass* %receiverClass, i32 %callSiteOffset) -declare %TBlock* @createBlock(%TContext* %callingContext, i8 %argLocation, i16 %bytePointer) +declare %TBlock* @createBlock(%TContext* %callingContext, i8 %argLocation, i16 %bytePointer, i32 %stackTop) declare { %TObject*, %TContext* } @invokeBlock(%TBlock* %block, %TContext* %callingContext) ;declare %TObject* @invokeBlock(%TBlock* %block, %TContext* %callingContext) From c9af814e65a4d9d3a397c93d74a9cd65cbb383d7 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 23:48:35 +0600 Subject: [PATCH 089/105] Adds MethodCompiler::insertTrace() for JIT code trace injection Issue: #17 Issue: #92 --- include/Core.ll | 2 ++ include/jit.h | 3 +++ src/MethodCompiler.cpp | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/include/Core.ll b/include/Core.ll index 66fc1c0..7c3c0b7 100644 --- a/include/Core.ll +++ b/include/Core.ll @@ -350,6 +350,8 @@ declare %TObject* @callPrimitive(i8 %opcode, %TObjectArray* %args, i1* %primitiv %TContext* ; targetContext } +declare i32 @printf(i8* noalias nocapture, ...) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; exception API ;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/include/jit.h b/include/jit.h index 10e17b1..1f45c68 100644 --- a/include/jit.h +++ b/include/jit.h @@ -398,6 +398,9 @@ class MethodCompiler { TStackObject allocateStackObject(llvm::IRBuilder<>& builder, uint32_t baseSize, uint32_t fieldsCount); + void insertTrace(TJITContext& jit, const char* message); + void insertTrace(TJITContext& jit, const char* message, llvm::Value* value); + MethodCompiler( JITRuntime& runtime, llvm::Module* JITModule, diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index b9d60bc..98afcc9 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -56,6 +56,28 @@ std::string to_string(const T& x) { return ss.str(); } +static Constant* createStringConstant(Module& M, char const* str, Twine const& name) { + LLVMContext& ctx = getGlobalContext(); + Constant* strConstant = ConstantDataArray::getString(ctx, str); + GlobalVariable* GVStr = + new GlobalVariable(M, strConstant->getType(), true, + GlobalValue::InternalLinkage, strConstant, name); + Constant* zero = Constant::getNullValue(IntegerType::getInt32Ty(ctx)); + Constant* indices[] = {zero, zero}; + Constant* strVal = ConstantExpr::getGetElementPtr(GVStr, indices, true); + return strVal; +} + +void MethodCompiler::insertTrace(MethodCompiler::TJITContext& jit, const char* message) { + Value* const print = m_JITModule->getFunction("printf"); + jit.builder->CreateCall(print, createStringConstant(*m_JITModule, message, "str.")); +} + +void MethodCompiler::insertTrace(MethodCompiler::TJITContext& jit, const char* message, llvm::Value* value) { + Value* const print = m_JITModule->getFunction("printf"); + jit.builder->CreateCall2(print, createStringConstant(*m_JITModule, message, "str."), value); +} + MethodCompiler::MethodCompiler( JITRuntime& runtime, llvm::Module* JITModule, From 15a7e2a38917de7749cbac567e827b7847624347 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 23:49:15 +0600 Subject: [PATCH 090/105] Fixes Type::toString() Issue: #17 Issue: #92 --- include/inference.h | 4 ++-- src/TypeAnalyzer.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/inference.h b/include/inference.h index eb62e33..83ab0a2 100644 --- a/include/inference.h +++ b/include/inference.h @@ -39,7 +39,7 @@ class Type { // tkMonotype (class name) (SmallInt) // tkComposite (class name, ...) (SmallInt, *) // tkArray class name [...] Array[String, *, (*, *), (True, False)] - std::string toString(bool subtypesOnly = false) const; + std::string toString(bool subtypesOnly = true) const; Type(TKind kind = tkUndefined) : m_kind(kind), m_value(0) {} Type(TObject* literal, TKind kind = tkLiteral) { set(literal, kind); } @@ -79,7 +79,7 @@ class Type { bool isBlock() const { return isMonotype() && - m_value == globals.blockClass && + (m_value == globals.blockClass) && !m_subTypes.empty(); } diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 2ac2dbb..8b29892 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -26,7 +26,7 @@ static void printBlock(const Type& blockType, std::stringstream& stream) { stream << blockType[Type::bstCaptureIndex].toString(); // capture context index } -std::string Type::toString(bool subtypesOnly /*= false*/) const { +std::string Type::toString(bool subtypesOnly /*= true*/) const { std::stringstream stream; switch (m_kind) { @@ -62,7 +62,7 @@ std::string Type::toString(bool subtypesOnly /*= false*/) const { break; case tkArray: - if (subtypesOnly) + if (!subtypesOnly) stream << getValue()->cast()->name->toString(); case tkComposite: { stream << (m_kind == tkComposite ? "(" : "["); From 3daecdb6dd0dd5e0c010a8f991a6391c0d9c5f72 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sun, 17 Jul 2016 23:52:58 +0600 Subject: [PATCH 091/105] Fixes compiler and runtime related to dynamic block dispatch Issue: #17 Issue: #92 --- include/inference.h | 4 +-- include/jit.h | 4 +-- src/JITRuntime.cpp | 5 +-- src/MethodCompiler.cpp | 70 +++++++++++++++++++++++++++++------------- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/include/inference.h b/include/inference.h index 83ab0a2..b116fa7 100644 --- a/include/inference.h +++ b/include/inference.h @@ -287,8 +287,8 @@ typedef std::map TTypeMap; inline std::string getQualifiedMethodName(TMethod* method, const Type& arguments) { return - arguments.toString(true) + "::" + - method->getClass()->name->toString() + ">>" + + arguments.toString() + "::" + + method->klass->name->toString() + ">>" + method->name->toString(); } diff --git a/include/jit.h b/include/jit.h index 1f45c68..ad3b2ca 100644 --- a/include/jit.h +++ b/include/jit.h @@ -338,7 +338,7 @@ class MethodCompiler { void doPushBlock(TJITContext& jit); llvm::Function* compileBlock(const std::string& blockFunctionName, st::ParsedBlock* parsedBlock, type::InferContext& blockContext); - llvm::Function* compileInvokedBlock(TJITContext& jit); + llvm::Function* compileInferredBlock(TJITContext& jit); void doAssignTemporary(TJITContext& jit); void doAssignInstance(TJITContext& jit); @@ -385,7 +385,7 @@ class MethodCompiler { llvm::Value** contextHolder = 0 ); - llvm::Function* compileBlock(TBlock* block); + llvm::Function* compileDynamicBlock(TBlock* block); // TStackObject is a pair of entities allocated on a thread stack space // objectSlot is a container for actual object's data diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 489289d..0369e88 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -586,11 +586,8 @@ void JITRuntime::invokeBlock(TBlock* block, TContext* callingContext, TReturnVal Function* blockFunction = 0; if (! compiledBlockFunction) { - // Block functions are created when wrapping method gets compiled. - // If function was not found then the whole method needs compilation. - // Compiling function and storing it to the table for further use - blockFunction = m_methodCompiler->compileBlock(block); + blockFunction = m_methodCompiler->compileDynamicBlock(block); if (!blockFunction) { // Something is really wrong! diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 98afcc9..90656ad 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -917,6 +917,7 @@ void createBlockTypes( st::ControlGraph& blockGraph, type::Type& blockType, type::Type& blockArguments, + type::Type& blockTemps, type::InferContext::TVariableMap& closureTypes) { blockType.set(globals.blockClass, type::Type::tkMonotype); @@ -931,6 +932,11 @@ void createBlockTypes( const uint32_t argumentLocation = block->argumentLocation; const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; + const st::ControlGraph::TMetaInfo::TIndexList& writesTemporaries = blockGraph.getMeta().writesTemporaries; + + type::Type readIndices(globals.arrayClass, type::Type::tkArray); + type::Type writeIndices(globals.arrayClass, type::Type::tkArray); + for (std::size_t index = 0; index < readsTemporaries.size(); index++) { const uint32_t tempIndex = readsTemporaries[index]; TObject* const argument = temporaries[tempIndex]; @@ -940,17 +946,37 @@ void createBlockTypes( if (readsTemporaries[index] < argumentLocation) closureTypes[tempIndex] = newType; else - blockArguments.pushSubType(newType); + blockTemps.pushSubType(newType); + + // We're interested only in temporaries from lexical context, not block arguments + if (readsTemporaries[index] < argumentLocation) + readIndices.pushSubType(type::Type(TInteger(readsTemporaries[index]))); + } + + for (std::size_t index = 0; index < writesTemporaries.size(); index++) { + // We're interested only in temporaries from lexical context, not block arguments + if (writesTemporaries[index] >= argumentLocation) + continue; + + writeIndices.pushSubType(type::Type(TInteger(writesTemporaries[index]))); } + blockType.pushSubType(readIndices); // [Type::bstReadsTemps] + blockType.pushSubType(writeIndices); // [Type::bstWritesTemps] + blockType.pushSubType(type::Type(TInteger(0))); // [Type::bstCaptureIndex] } -std::string getBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments) +std::string getDynamicBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments, const type::Type& blockTemps) +{ + return blockArguments.toString() + blockTemps.toString() + "::" + blockType.toString(); +} + +std::string getStaticBlockFunctionName(const type::Type& blockType, const type::Type& blockArguments) { return blockArguments.toString() + "::" + blockType.toString(); } -llvm::Function* MethodCompiler::compileBlock(TBlock* block) +llvm::Function* MethodCompiler::compileDynamicBlock(TBlock* block) { const uint16_t blockOffset = block->blockBytePointer; st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(block->method); @@ -964,10 +990,11 @@ llvm::Function* MethodCompiler::compileBlock(TBlock* block) type::Type blockType; type::Type blockArguments(globals.arrayClass, type::Type::tkArray); - createBlockTypes(block, *blockGraph, blockType, blockArguments, closureTypes); + type::Type blockTemps(globals.arrayClass, type::Type::tkArray); + createBlockTypes(block, *blockGraph, blockType, blockArguments, blockTemps, closureTypes); // Check if function was already compiled - const std::string& blockFunctionName = getBlockFunctionName(blockType, blockArguments); + const std::string& blockFunctionName = getDynamicBlockFunctionName(blockType, blockArguments, blockTemps); if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) return blockFunction; @@ -978,10 +1005,10 @@ llvm::Function* MethodCompiler::compileBlock(TBlock* block) return compileBlock(blockFunctionName, parsedBlock, *blockInferContext); } -llvm::Function* MethodCompiler::compileInvokedBlock(TJITContext& jit) +llvm::Function* MethodCompiler::compileInferredBlock(TJITContext& jit) { type::Type& blockType = jit.inferContext[*jit.currentNode->getArgument()]; - if (blockType.isBlock()) + if (! blockType.isBlock()) return 0; type::Type blockArguments(type::Type::tkArray); @@ -992,13 +1019,12 @@ llvm::Function* MethodCompiler::compileInvokedBlock(TJITContext& jit) if (jit.currentNode->getArgumentsCount() > 2) blockArguments.pushSubType(jit.inferContext[*jit.currentNode->getArgument(2)]); - type::TContextStack stack(jit.inferContext); // Probably not needed, the context must already be cached - type::InferContext* const blockContext = m_typeSystem.inferBlock(blockType, blockArguments, &stack); + type::InferContext* const blockContext = m_typeSystem.inferBlock(blockType, blockArguments, 0); if (! blockContext) return 0; - const std::string& blockFunctionName = getBlockFunctionName(blockType, blockArguments); + const std::string& blockFunctionName = getStaticBlockFunctionName(blockType, blockArguments); if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) return blockFunction; @@ -1514,8 +1540,7 @@ void MethodCompiler::doSendMessage(TJITContext& jit) const uint32_t literalIndex = jit.currentNode->getInstruction().getArgument(); TSymbol* const selector = literals[literalIndex]; - type::InferContext* const messageContext = m_typeSystem.inferMessage(selector, arguments, 0, false); - if (messageContext) { + if (type::InferContext* const messageContext = m_typeSystem.inferMessage(selector, arguments, 0, false)) { doSendInferredMessage(jit, *messageContext); return; } @@ -1905,6 +1930,7 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, break; case primitive::blockInvoke: { // 8 + Value* const object = getArgument(jit); // jit.popValue(); Value* const block = jit.builder->CreateBitCast(object, m_baseTypes.block->getPointerTo()); @@ -1912,22 +1938,23 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, Value* const blockAsContext = jit.builder->CreateBitCast(block, m_baseTypes.context->getPointerTo()); Value* const blockTemps = jit.builder->CreateCall(m_baseFunctions.getTemps, blockAsContext); - Value* const tempsSize = jit.builder->CreateCall(m_baseFunctions.getObjectSize, blockTemps, "tempsSize."); +// Value* const tempsSize = jit.builder->CreateCall(m_baseFunctions.getObjectSize, blockTemps, "tempsSize."); Value* const argumentLocationPtr = jit.builder->CreateStructGEP(block, 1); Value* const argumentLocationField = jit.builder->CreateLoad(argumentLocationPtr); Value* const argumentLocationObject = jit.builder->CreateIntToPtr(argumentLocationField, m_baseTypes.object->getPointerTo()); Value* const argumentLocation = jit.builder->CreateCall(m_baseFunctions.getIntegerValue, argumentLocationObject, "argLocation."); - BasicBlock* const tempsChecked = BasicBlock::Create(m_JITModule->getContext(), "tempsChecked.", jit.function); - tempsChecked->moveAfter(jit.builder->GetInsertBlock()); +// BasicBlock* const tempsChecked = BasicBlock::Create(m_JITModule->getContext(), "tempsChecked.", jit.function); +// tempsChecked->moveAfter(jit.builder->GetInsertBlock()); + // FIXME Check will lead to a crash if containing method have no temporaries at all // TODO Remove the check if block is inferred //Checking the passed temps size TODO unroll stack - Value* const blockAcceptsArgCount = jit.builder->CreateSub(tempsSize, argumentLocation, "blockAcceptsArgCount."); - Value* const tempSizeOk = jit.builder->CreateICmpSLE(jit.builder->getInt32(argCount), blockAcceptsArgCount, "tempSizeOk."); - jit.builder->CreateCondBr(tempSizeOk, tempsChecked, primitiveFailedBB); - jit.builder->SetInsertPoint(tempsChecked); +// Value* const blockAcceptsArgCount = jit.builder->CreateSub(tempsSize, argumentLocation, "blockAcceptsArgCount."); +// Value* const tempSizeOk = jit.builder->CreateICmpSLE(jit.builder->getInt32(argCount), blockAcceptsArgCount, "tempSizeOk."); +// jit.builder->CreateCondBr(tempSizeOk, tempsChecked, primitiveFailedBB); +// jit.builder->SetInsertPoint(tempsChecked); // Storing values in the block's wrapping context for (uint32_t index = argCount - 1, count = argCount; count > 0; index--, count--) @@ -1939,12 +1966,13 @@ void MethodCompiler::compilePrimitive(TJITContext& jit, jit.builder->CreateCall3(m_baseFunctions.setObjectField, blockTemps, fieldIndex, argument); } - if (Function* const blockFunction = compileInvokedBlock(jit)) + if (Function* const blockFunction = compileInferredBlock(jit)) { // Direct call to the inferred block function primitiveResult = jit.builder->CreateCall(blockFunction, block); - else + } else { // Generic dispatch using runtime primitiveResult = jit.builder->CreateCall2(m_runtimeAPI.invokeBlock, block, jit.getCurrentContext()); + } } break; case primitive::throwError: { //19 From f17ef79b6cae1eae2e832d6281a4a128a9ae6072 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 21 Jul 2016 21:33:02 +0600 Subject: [PATCH 092/105] Various fixes of method compiler, control graph and type analyzer Issue: #17 Issue: #92 --- include/inference.h | 2 +- include/types.h | 2 ++ src/ControlGraph.cpp | 4 ++++ src/JITRuntime.cpp | 3 ++- src/MethodCompiler.cpp | 24 +++++++++++++----------- src/TypeAnalyzer.cpp | 3 +++ 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/include/inference.h b/include/inference.h index b116fa7..2979934 100644 --- a/include/inference.h +++ b/include/inference.h @@ -387,7 +387,7 @@ struct TContextStack { class TypeSystem { public: - TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(0) {} + TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(1) {} typedef TSymbol* TSelector; diff --git a/include/types.h b/include/types.h index 0116758..44720f1 100644 --- a/include/types.h +++ b/include/types.h @@ -70,6 +70,8 @@ struct TInteger { int32_t operator -(int32_t right) const { return getValue() - right; } operator TObject*() const { return reinterpret_cast(m_value); } + void setRawValue(int32_t value) { m_value = value; } + private: int32_t m_value; protected: diff --git a/src/ControlGraph.cpp b/src/ControlGraph.cpp index 5250735..5530e2c 100644 --- a/src/ControlGraph.cpp +++ b/src/ControlGraph.cpp @@ -190,9 +190,13 @@ void GraphConstructor::processNode(InstructionNode* node) if (instruction.getArgument() == 0) m_graph->getMeta().usesSelf = true; ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsArguments); + m_currentDomain->pushValue(node); + break; case opcode::pushInstance: m_graph->getMeta().readsFields = true; + m_currentDomain->pushValue(node); + break; case opcode::pushTemporary: ControlGraph::TMetaInfo::insertIndex(instruction.getArgument(), m_graph->getMeta().readsTemporaries); diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index 0369e88..baacd50 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -226,7 +226,7 @@ TBlock* JITRuntime::createBlock(TContext* callingContext, uint8_t argLocation, u // NOTE This field is used by JIT VM to aid specialization lookup // See MethodCompiler::getClosureMask() and lookupBlockFunctionInCache() - newBlock->stackTop = stackTop; + newBlock->stackTop.setRawValue(stackTop); // Assigning creatingContext depending on the hierarchy // Nested blocks inherit the outer creating context @@ -557,6 +557,7 @@ void JITRuntime::updateBlockFunctionCache(TBlock* block, TBlockFunction function // We have found a slot that may be used fillBlockSlot(slot, block, function); + return; } // It seem that all slots are occupied by the friendly specializations. diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 90656ad..916ba0b 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -928,7 +928,7 @@ void createBlockTypes( blockArguments.set(globals.arrayClass, type::Type::tkArray); - TObjectArray& temporaries = * block->creatingContext->temporaries; + TObjectArray& temporaries = * block->temporaries; const uint32_t argumentLocation = block->argumentLocation; const st::ControlGraph::TMetaInfo::TIndexList& readsTemporaries = blockGraph.getMeta().readsTemporaries; @@ -943,14 +943,13 @@ void createBlockTypes( TClass* const klass = isSmallInteger(argument) ? globals.smallIntClass : argument->getClass(); const type::Type newType(klass); - if (readsTemporaries[index] < argumentLocation) + if (readsTemporaries[index] < argumentLocation) { closureTypes[tempIndex] = newType; - else blockTemps.pushSubType(newType); - - // We're interested only in temporaries from lexical context, not block arguments - if (readsTemporaries[index] < argumentLocation) - readIndices.pushSubType(type::Type(TInteger(readsTemporaries[index]))); + readIndices.pushSubType(type::Type(TInteger(tempIndex))); + } else { + blockArguments.pushSubType(newType); + } } for (std::size_t index = 0; index < writesTemporaries.size(); index++) { @@ -1308,10 +1307,13 @@ void MethodCompiler::doSendBinary(TJITContext& jit) const type::Type& resultType = jit.inferContext[*jit.currentNode]; if (leftType.isLiteral() && rightType.isLiteral()) { - ConstantInt* const rawResult = jit.builder->getInt32(TInteger(resultType.getValue())); - Value* const result = jit.builder->CreateCall(m_baseFunctions.newInteger, rawResult); - setNodeValue(jit, jit.currentNode, result); - return; + // TODO Extend to all literals + if (isSmallInteger(resultType.getValue())) { + ConstantInt* const rawResult = jit.builder->getInt32(TInteger(resultType.getValue())); + Value* const result = jit.builder->CreateCall(m_baseFunctions.newInteger, rawResult); + setNodeValue(jit, jit.currentNode, result); + return; + } } // Literal int or (SmallInt) monotype diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 8b29892..f559eb2 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -202,6 +202,7 @@ void TypeAnalyzer::run(const Type* blockType /*= 0*/) { m_baseRun = true; m_literalBranch = true; + m_tauLinker.reset(); m_tauLinker.run(); bool singleReturn = basicRun(); @@ -564,6 +565,8 @@ void TypeAnalyzer::doPushTemporary(const InstructionNode& instruction) { InferContext::TVariableMap& closureTypes = methodContext->getBlockClosures()[captureIndex]; m_context[instruction] = closureTypes[tempIndex]; + } else { + std::cout << "Could not find method context for block " << m_blockType->toString() << std::endl; } } } else { From ee7d46999d0fb2435f6fff550e510c1b8ef6920d Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 21 Jul 2016 21:50:56 +0600 Subject: [PATCH 093/105] Adds special handling of dynamically inferred blocks Unlike statically inferred block, dynamic ones need to create and carry their closure environment. Two blocks with the same set of argument types may have different closure types even if their closure context is the same. We add closure types to a dynamic block's cache key and to block function name to disambiguate calls. Issue: #17 Issue: #92 --- include/inference.h | 8 +++++++ src/JITRuntime.cpp | 2 ++ src/MethodCompiler.cpp | 7 +++--- src/TypeAnalyzer.cpp | 52 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/include/inference.h b/include/inference.h index 2979934..46a1fcd 100644 --- a/include/inference.h +++ b/include/inference.h @@ -398,12 +398,20 @@ class TypeSystem { bool sendToSuper = false); InferContext* inferBlock(Type& block, const Type& arguments, TContextStack* parent); + InferContext* inferDynamicBlock(Type& block, const Type& arguments, const Type& temporaries, TContextStack* parent); + + // TODO Solve concurrent modifications by returning: + // - R/O holder to the shared graph + // - R/W holder to the exclusive copy st::ControlGraph* getMethodGraph(TMethod* method); st::ControlGraph* getBlockGraph(st::ParsedBlock* parsedBlock); void dumpAllContexts() const; void drawCallGraph() const; +private: + void doInferBlock(InferContext* inferContext, Type& block, const Type& arguments, TContextStack* parent); + private: typedef std::pair TGraphEntry; typedef std::map TGraphCache; diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index baacd50..c1ecbbd 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -674,6 +674,8 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject Function* methodFunction = m_JITModule->getFunction(functionName); if (! methodFunction) { + outs() << "Compiling dynamic method " << functionName << "\n"; + // Compiling function and storing it to the table for further use methodFunction = m_methodCompiler->compileMethod(method, argumentsType); diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 916ba0b..1ccd389 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -997,8 +997,10 @@ llvm::Function* MethodCompiler::compileDynamicBlock(TBlock* block) if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) return blockFunction; + printf("compiling dynamic block %s\n", blockFunctionName.c_str()); + type::TContextStack stack(methodContext); - type::InferContext* const blockInferContext = m_typeSystem.inferBlock(blockType, blockArguments, &stack); + type::InferContext* const blockInferContext = m_typeSystem.inferDynamicBlock(blockType, blockArguments, blockTemps, &stack); assert(blockInferContext); return compileBlock(blockFunctionName, parsedBlock, *blockInferContext); @@ -1033,6 +1035,7 @@ llvm::Function* MethodCompiler::compileInferredBlock(TJITContext& jit) st::ControlGraph* const methodGraph = m_typeSystem.getMethodGraph(method); st::ParsedBlock* const parsedBlock = methodGraph->getParsedMethod()->getParsedBlockByOffset(blockOffset); + printf("compiling inferred block %s\n", blockFunctionName.c_str()); return compileBlock(blockFunctionName, parsedBlock, *blockContext); } @@ -1042,8 +1045,6 @@ llvm::Function* MethodCompiler::compileBlock(const std::string& blockFunctionNam if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) return blockFunction; - printf("compiling block %s\n", blockFunctionName.c_str()); - st::ControlGraph* const blockGraph = m_typeSystem.getBlockGraph(parsedBlock); TJITBlockContext blockContext(this, parsedBlock->getContainer(), parsedBlock, blockGraph, blockcontext); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f559eb2..34f8f3b 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1234,7 +1234,7 @@ InferContext* TypeSystem::inferMessage( const TContextMap::iterator iContext = contextMap.find(key); if (iContext != contextMap.end()) { InferContext* const cachedContext = iContext->second; - std::printf("*** Found cached context for key: %s, cache: %p\n", key.toString().c_str(), &contextMap); + std::printf("*** Found cached context for key: %s, selector: %s\n", key.toString().c_str(), selector->toString().c_str()); if (cachedContext->getRecursionKind() == InferContext::rkUnknown) { for (TContextStack* stack = parent; stack; stack = stack->parent) { @@ -1255,7 +1255,7 @@ InferContext* TypeSystem::inferMessage( return cachedContext; } - std::printf("*** Not found cached context for key: %s, cache: %p\n", key.toString().c_str(), &contextMap); + std::printf("*** Not found cached context for key: %s, selector: %s\n", key.toString().c_str(), selector->toString().c_str()); } TClass* receiver = 0; @@ -1313,29 +1313,63 @@ InferContext* TypeSystem::inferMessage( return inferContext; } -InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContextStack* parent) { - if (block.getKind() != Type::tkMonotype) +InferContext* TypeSystem::inferDynamicBlock( + Type& block, + const Type& arguments, + const Type& temporaries, + TContextStack* parent) +{ + if (! block.isBlock()) return 0; - // FIXME Prove that this is enough. - // What about captured types/args? Type key(Type::tkArray); key.pushSubType(block); key.pushSubType(arguments); + key.pushSubType(temporaries); TBlockCache::iterator iBlock = m_blockCache.find(key); if (iBlock != m_blockCache.end()) return iBlock->second; TMethod* const method = block[Type::bstOrigin].getValue()->cast(); - const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); + m_blockCache[key] = inferContext; + std::printf("Cached dynamic block context %s -> %p (index %u), cache size %u\n", + key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); - InferContext* inferContext = new InferContext(method, m_lastContextIndex++, arguments); + doInferBlock(inferContext, block, arguments, parent); + + return inferContext; +} + +InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContextStack* parent) { + if (! block.isBlock()) + return 0; + + Type key(Type::tkArray); + key.pushSubType(block); + key.pushSubType(arguments); + + TBlockCache::iterator iBlock = m_blockCache.find(key); + if (iBlock != m_blockCache.end()) + return iBlock->second; + + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + InferContext* const inferContext = new InferContext(method, m_lastContextIndex++, arguments); m_blockCache[key] = inferContext; std::printf("Cached block context %s -> %p (index %u), cache size %u\n", key.toString().c_str(), inferContext, inferContext->getIndex(), m_blockCache.size()); + doInferBlock(inferContext, block, arguments, parent); + return inferContext; + +} + +void TypeSystem::doInferBlock(InferContext* inferContext, Type& block, const Type& arguments, TContextStack* parent) { + TMethod* const method = block[Type::bstOrigin].getValue()->cast(); + const uint16_t offset = TInteger(block[Type::bstOffset].getValue()); + ControlGraph* const methodGraph = getMethodGraph(method); assert(methodGraph); @@ -1366,8 +1400,6 @@ InferContext* TypeSystem::inferBlock(Type& block, const Type& arguments, TContex std::printf("%s::%s -> %s\n", arguments.toString().c_str(), block.toString().c_str(), inferContext->getRawReturnType().toString().c_str()); - - return inferContext; } void type::TypeSystem::dumpAllContexts() const { From a532ac01f8a175af36d90bcee9c3f3d3dcb483fc Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 21 Jul 2016 21:56:20 +0600 Subject: [PATCH 094/105] Adds TypeSystem::findBlockContext() Issue: #17 Issue: #92 --- include/inference.h | 2 ++ src/TypeAnalyzer.cpp | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/inference.h b/include/inference.h index 46a1fcd..3ec5b21 100644 --- a/include/inference.h +++ b/include/inference.h @@ -391,6 +391,8 @@ class TypeSystem { typedef TSymbol* TSelector; + InferContext* findBlockContext(std::size_t contextIndex) const; + InferContext* inferMessage( TSelector selector, const Type& arguments, diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index 34f8f3b..f447000 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1485,3 +1485,15 @@ void type::TypeSystem::drawCallGraph() const { stream << "}\n"; stream.close(); } + +type::InferContext* type::TypeSystem::findBlockContext(std::size_t contextIndex) const +{ + TBlockCache::const_iterator iContext = m_blockCache.begin(); + for (; iContext != m_blockCache.end(); ++iContext) { + InferContext* const candidate = iContext->second; + if (candidate->getIndex() == contextIndex) + return candidate; + } + + return 0; +} From 9741b4653f0743847682538156e8cb2787840f35 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 21 Jul 2016 22:18:28 +0600 Subject: [PATCH 095/105] Fixes sendToSuper Issue: #17 Issue: #92 --- src/JITRuntime.cpp | 2 -- src/TypeAnalyzer.cpp | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index c1ecbbd..f58382f 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -666,8 +666,6 @@ void JITRuntime::sendMessage(TContext* callingContext, TSymbol* message, TObject if (! compiledMethodFunction) { type::Type argumentsType = type::createArgumentsType(messageArguments); - if (receiverClass) - argumentsType[0].set(receiverClass); // If function was not found in the cache looking it in the LLVM directly const std::string& functionName = type::getQualifiedMethodName(method, argumentsType); diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f447000..efc5922 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -1271,10 +1271,7 @@ InferContext* TypeSystem::inferMessage( receiver = self.getValue()->cast(); } - if (sendToSuper) - receiver = receiver->parentClass; - - TMethod* const method = m_vm.lookupMethod(selector, receiver); + TMethod* const method = m_vm.lookupMethod(selector, sendToSuper ? receiver->parentClass : receiver); if (! method) { // TODO Redirect to #doesNotUnderstand: statically std::printf("Lookup failed for %s::?>>%s...\n", From 206db1c279cf68fda76ce4655a3b1dc33c59db73 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 22 Jul 2016 00:20:43 +0600 Subject: [PATCH 096/105] Refactors JITRuntime::printStat() Issue: #17 Issue: #92 --- include/jit.h | 1 + src/JITRuntime.cpp | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/include/jit.h b/include/jit.h index ad3b2ca..ec8e5c0 100644 --- a/include/jit.h +++ b/include/jit.h @@ -292,6 +292,7 @@ class MethodCompiler { } }; + type::TypeSystem& getTypeSystem() { return m_typeSystem; } private: JITRuntime& m_runtime; diff --git a/src/JITRuntime.cpp b/src/JITRuntime.cpp index f58382f..a47ef20 100644 --- a/src/JITRuntime.cpp +++ b/src/JITRuntime.cpp @@ -68,25 +68,23 @@ static bool compareByHitCount(const JITRuntime::THotMethod* m1, const JITRuntime void JITRuntime::printStat() { - float hitRatio = 100.0 * m_cacheHits / (m_cacheHits + m_cacheMisses); - float blockHitRatio = 100.0 * m_blockCacheHits / (m_blockCacheHits + m_blockCacheMisses); + float messagehitRatio = 100.0 * m_cacheHits / (m_cacheHits + m_cacheMisses); + float blockHitRatio = 100.0 * m_blockCacheHits / (m_blockCacheHits + m_blockCacheMisses); std::printf( "JIT Runtime stat:\n" - "\tMessages dispatched: %12d\n" - "\tObjects allocated: %12d\n" - "\tBlocks invoked: %12d\n" - "\tBlockReturn emitted: %12d\n" - "\tBlock cache hits: %12d misses %10d ratio %6.2f %%\n" - "\tMessage cache hits: %12d misses %10d ratio %6.2f %%\n", - - m_messagesDispatched, - m_objectsAllocated, - m_blocksInvoked, m_blockReturnsEmitted, - m_blockCacheHits, m_blockCacheMisses, blockHitRatio, - m_cacheHits, m_cacheMisses, hitRatio + "\tDynamic messages: %12d total, %12d hits, %10d misses, hit ratio %6.2f %%\n" + "\tDynamic blocks: %12d total, %12d hits, %10d misses, hit ratio %6.2f %%\n" + "\tObjects allocated: %12d\n", + + m_messagesDispatched, m_cacheHits, m_cacheMisses, messagehitRatio, + m_blocksInvoked, m_blockCacheHits, m_blockCacheMisses, blockHitRatio, + m_objectsAllocated ); + m_methodCompiler->getTypeSystem().dumpAllContexts(); + m_methodCompiler->getTypeSystem().drawCallGraph(); + std::vector hotMethods; for (THotMethodsMap::iterator iMethod = m_hotMethods.begin(); iMethod != m_hotMethods.end(); ++iMethod) From 33f4452d57e35854a947e9fd2e10021bdd1b618e Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Mon, 25 Jul 2016 23:59:15 +0700 Subject: [PATCH 097/105] Folds argument types of SendBinary instruction Issue: #17 Issue: #92 --- src/MethodCompiler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index 1ccd389..d6c7c3f 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -1303,9 +1303,9 @@ void MethodCompiler::setNodeValue(TJITContext& jit, st::ControlNode* node, llvm: void MethodCompiler::doSendBinary(TJITContext& jit) { - const type::Type& leftType = jit.inferContext[*jit.currentNode->getArgument(0)]; - const type::Type& rightType = jit.inferContext[*jit.currentNode->getArgument(1)]; - const type::Type& resultType = jit.inferContext[*jit.currentNode]; + const type::Type& leftType = jit.inferContext[*jit.currentNode->getArgument(0)].fold(); + const type::Type& rightType = jit.inferContext[*jit.currentNode->getArgument(1)].fold(); + const type::Type& resultType = jit.inferContext[*jit.currentNode].fold(); if (leftType.isLiteral() && rightType.isLiteral()) { // TODO Extend to all literals From fc6d1dde1849aacf79dbba35a321d999dda708dd Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 26 Jul 2016 00:00:12 +0700 Subject: [PATCH 098/105] Fixes primitives inference Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index efc5922..f084023 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -977,15 +977,22 @@ void TypeAnalyzer::doPrimitive(const InstructionNode& instruction) { } case primitive::arrayAt: - case primitive::arrayAtPut: - case primitive::stringAt: - case primitive::stringAtPut: case primitive::bulkReplace: primitiveResult = Type(Type::tkPolytype); break; + // FIXME actually this is primitive for ByteArray>>basicAt: + case primitive::stringAt: + primitiveResult = Type(globals.smallIntClass, Type::tkMonotype); + break; + + case primitive::arrayAtPut: + case primitive::stringAtPut: + primitiveResult = m_context[*instruction.getArgument(1)]; + break; + case primitive::cloneByteObject: - primitiveResult = m_context[*instruction.getArgument(0)]; + primitiveResult = m_context[*instruction.getArgument(1)]; break; case primitive::blockInvoke: { From 878db08968aae091a8a5ee57276db468dd7bd048 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Tue, 2 Aug 2016 22:02:23 +0700 Subject: [PATCH 099/105] Fixes PushBlock in nested block context Issue: #17 Issue: #92 --- src/TypeAnalyzer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/TypeAnalyzer.cpp b/src/TypeAnalyzer.cpp index f084023..be2e119 100644 --- a/src/TypeAnalyzer.cpp +++ b/src/TypeAnalyzer.cpp @@ -628,7 +628,11 @@ void TypeAnalyzer::doPushBlock(const InstructionNode& instruction) { blockType.pushSubType(origin); // [Type::bstOrigin] blockType.pushSubType(Type(TInteger(offset))); // [Type::bstOffset] blockType.pushSubType(Type(TInteger(argIndex))); // [Type::bstArgIndex] - blockType.pushSubType(Type(TInteger(m_context.getIndex()))); // [Type::bstContextIndex] + + if (m_blockType) + blockType.pushSubType((*m_blockType)[Type::bstContextIndex]); + else + blockType.pushSubType(Type(TInteger(m_context.getIndex()))); // [Type::bstContextIndex] ControlGraph* const blockGraph = m_system.getBlockGraph(pushBlock->getParsedBlock()); assert(blockGraph); From 6a1167e1f391691508b01b3190e066ad166cafee Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Sat, 6 Aug 2016 11:42:13 +0700 Subject: [PATCH 100/105] Adds closure mask for dynamic blocks created in soft VM Issue: #17 Issue: #92 --- src/MethodCompiler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/MethodCompiler.cpp b/src/MethodCompiler.cpp index d6c7c3f..9a773c1 100644 --- a/src/MethodCompiler.cpp +++ b/src/MethodCompiler.cpp @@ -992,6 +992,9 @@ llvm::Function* MethodCompiler::compileDynamicBlock(TBlock* block) type::Type blockTemps(globals.arrayClass, type::Type::tkArray); createBlockTypes(block, *blockGraph, blockType, blockArguments, blockTemps, closureTypes); + const uint32_t closureMask = getClosureMask(*blockGraph); + block->stackTop.setRawValue(closureMask); + // Check if function was already compiled const std::string& blockFunctionName = getDynamicBlockFunctionName(blockType, blockArguments, blockTemps); if (Function* const blockFunction = m_JITModule->getFunction(blockFunctionName)) From aefcc90b38c60bd4e1d86b39f4b0340d38b04402 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sat, 13 Aug 2016 02:42:06 +0300 Subject: [PATCH 101/105] Fixes H_VMImage to test VM primitives Issue: #92 --- tests/helpers/VMImage.h | 11 ++++++----- tests/vm_primitives.cpp | 23 ++++++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/tests/helpers/VMImage.h b/tests/helpers/VMImage.h index 26fb851..0386d57 100644 --- a/tests/helpers/VMImage.h +++ b/tests/helpers/VMImage.h @@ -11,11 +11,12 @@ class H_VMImage std::auto_ptr m_memoryManager; std::auto_ptr m_smalltalkImage; public: - H_VMImage(const std::string& imageName) { - std::auto_ptr memoryManager(new BakerMemoryManager()); - memoryManager->initializeHeap(1024*1024, 1024*1024); - std::auto_ptr smalltalkImage(new Image(memoryManager.get())); - smalltalkImage->loadImage(TESTS_DIR "./data/" + imageName + ".image"); + H_VMImage(const std::string& imageName) : + m_memoryManager(new BakerMemoryManager()), + m_smalltalkImage(new Image(m_memoryManager.get())) + { + m_memoryManager->initializeHeap(1024*1024, 1024*1024); + m_smalltalkImage->loadImage(TESTS_DIR "./data/" + imageName + ".image"); } TObjectArray* newArray(std::size_t fields); TString* newString(std::size_t size); diff --git a/tests/vm_primitives.cpp b/tests/vm_primitives.cpp index 40092a5..c797053 100644 --- a/tests/vm_primitives.cpp +++ b/tests/vm_primitives.cpp @@ -1,9 +1,30 @@ #include #include "helpers/VMImage.h" -#include "patterns/InitVMImage.h" #include #include +class P_InitVM_Image : public ::testing::TestWithParam +{ +protected: + H_VMImage* m_image; +public: + virtual ~P_InitVM_Image() {} + + virtual void SetUp() + { + bool is_parameterized_test = ::testing::UnitTest::GetInstance()->current_test_info()->value_param(); + if (!is_parameterized_test) { + // Use TEST_P instead of TEST_F ! + abort(); + } + ParamType image_name = GetParam(); + m_image = new H_VMImage(image_name); + } + virtual void TearDown() { + delete m_image; + } +}; + INSTANTIATE_TEST_CASE_P(_, P_InitVM_Image, ::testing::Values(std::string("VMPrimitives")) ); TEST_P(P_InitVM_Image, smallint) From 63f134e1e03d40ba276c1826a5e6ea7ab0cb8d62 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sat, 13 Aug 2016 07:18:53 +0300 Subject: [PATCH 102/105] Frees mem in TypeSystem Issue: #92 --- include/inference.h | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/inference.h b/include/inference.h index 3ec5b21..95df6be 100644 --- a/include/inference.h +++ b/include/inference.h @@ -387,7 +387,34 @@ struct TContextStack { class TypeSystem { public: - TypeSystem(SmalltalkVM& vm) : m_vm(vm), m_lastContextIndex(1) {} + TypeSystem(SmalltalkVM& vm) : + m_vm(vm), + m_graphCache(), + m_contextCache(), + m_blockCache(), + m_blockGraphCache(), + m_lastContextIndex(1) + {} + + ~TypeSystem() { + for(TBlockGraphCache::const_iterator it = m_blockGraphCache.begin(); it != m_blockGraphCache.end(); ++it) { + delete it->second; + } + for(TBlockCache::const_iterator it = m_blockCache.begin(); it != m_blockCache.end(); ++it) { + delete it->second; + } + for(TContextCache::const_iterator it = m_contextCache.begin(); it != m_contextCache.end(); ++it) { + const TContextMap& contextMap = it->second; + for(TContextMap::const_iterator jt = contextMap.begin(); jt != contextMap.end(); ++jt) { + delete jt->second; + } + } + for(TGraphCache::const_iterator it = m_graphCache.begin(); it != m_graphCache.end(); ++it) { + const TGraphEntry& entry = it->second; + delete entry.first; + delete entry.second; + } + } typedef TSymbol* TSelector; From 62019e75956b5bb5513dc291c041145fd2835bf8 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sat, 13 Aug 2016 17:18:40 +0300 Subject: [PATCH 103/105] Fixes case 2 & (SmallInt) -> (SmallInt) Issue: #92 --- include/inference.h | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/include/inference.h b/include/inference.h index 95df6be..4cbff36 100644 --- a/include/inference.h +++ b/include/inference.h @@ -221,11 +221,33 @@ class Type { case tkLiteral: if (m_value == other.m_value) { // 2 & 3 - return *this; // 2 & 2 + return *this; // 2 & 2 -> 2 } else { - // TODO true & false -> (Boolean) - TClass* const klass = isSmallInteger(m_value) ? globals.smallIntClass : m_value->getClass(); - return *this = (Type(klass) &= other); + TClass* const leftClass = isSmallInteger(m_value) ? globals.smallIntClass : m_value->getClass(); + + if (other.m_kind == tkMonotype) { // 2 & (SmallInt) + TClass* const rightClass = static_cast(other.m_value); + if (leftClass == rightClass) { + // 2 & (SmallInt) -> (SmallInt) + return *this = (Type(leftClass) &= other); + } else { + // String & (SmallInt) -> * + return *this = Type(tkPolytype); + } + } else { // literal & literal + TClass* const rightClass = isSmallInteger(other.m_value) ? globals.smallIntClass : other.m_value->getClass(); + if (leftClass == rightClass) { + // 2 & 3 -> (SmallInt) + return *this = Type(leftClass); + } + TClass* const booleanClass = globals.trueObject->getClass()->parentClass; + if (leftClass->parentClass == rightClass->parentClass && leftClass->parentClass == booleanClass) { + // true & false -> Boolean + return *this = Type(booleanClass); + } + } + + return *this = (Type(leftClass) &= other); } case tkMonotype: { From 033a3e891c77de6ad6baaddfa5e0c1fd43fe3266 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sat, 13 Aug 2016 13:35:15 +0300 Subject: [PATCH 104/105] Fixes automkdir for graphviz Issue: #92 --- src/ControlGraphVisualizer.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/ControlGraphVisualizer.cpp b/src/ControlGraphVisualizer.cpp index 6a2874f..0951fb6 100644 --- a/src/ControlGraphVisualizer.cpp +++ b/src/ControlGraphVisualizer.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,25 @@ std::string gnu_getcwd() { } } +void gnu_mkdir(const std::string& path) { + int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (status != 0) { + std::stringstream ss; + ss << "Cannot create '" << path << "'"; + throw std::ios_base::failure( ss.str() ); + } +} + +bool gnu_dir_exists(const std::string& path) { + struct stat info; + int status = stat(path.c_str(), &info); + if (status != 0) + return false; + if (info.st_mode & S_IFDIR) + return true; + return false; +} + std::string escape_path(const std::string& path) { if (path.empty()) return ""; @@ -55,6 +75,9 @@ std::string escape_path(const std::string& path) { ControlGraphVisualizer::ControlGraphVisualizer(st::ControlGraph* graph, const std::string& fileName, const std::string& directory /*= "."*/) : st::PlainNodeVisitor(graph) { + if (!gnu_dir_exists(directory)) { + gnu_mkdir(directory); + } std::string fullpath = directory + "/" + escape_path(fileName) + ".dot"; m_stream.open(fullpath.c_str(), std::ios::out | std::ios::trunc); if (m_stream.fail()) { From 34c1048600b974b902fe21e6079baa329e1c1d5d Mon Sep 17 00:00:00 2001 From: Roman Proskuryakov Date: Sat, 13 Aug 2016 02:43:54 +0300 Subject: [PATCH 105/105] Adds basic tests and patterns for type inference Issue: #92 --- image/imageSource.st | 219 ++++++++++---- src/TauLinker.cpp | 2 +- tests/CMakeLists.txt | 4 + tests/data/Inference.image | Bin 0 -> 232051 bytes tests/inference.cpp | 541 +++++++++++++++++++++++++++++++++++ tests/patterns/InitVMImage.h | 22 +- 6 files changed, 728 insertions(+), 60 deletions(-) create mode 100755 tests/data/Inference.image create mode 100644 tests/inference.cpp diff --git a/image/imageSource.st b/image/imageSource.st index 1362582..264d585 100644 --- a/image/imageSource.st +++ b/image/imageSource.st @@ -776,18 +776,115 @@ COMMENT CLASS BranchTest Test METHOD BranchTest -ifTrue |x| +ifBlock |x| + [ true ] ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '1'. + + [ false ] ifFalse: [ x <- 151 ]. + [ x ] assertEq: 151 withComment: '2'. + [ true ] ifTrue: [ x <- 123 ] - ifFalse: [ x <- 151 ] - [ x ] assertEq: 123 -! + ifFalse: [ x <- 151 ]. + [ x ] assertEq: 123 withComment: '3'. + [ false ] ifFalse: [ x <- 521 ] + ifTrue: [ x <- 34 ]. + [ x ] assertEq: 521 withComment: '4'. +! METHOD BranchTest -ifFalse |x| +ifBlockBadCodegenIfTrue |x| [ false ] ifTrue: [ x <- 34 ] - ifFalse: [ x <- 521 ] - [ x ] assertEq: 521 + ifFalse: [ x <- 521 ]. + [ x ] assertEq: 521. +! + +METHOD BranchTest +ifBlockBadCodegenIfFalse |x| + [ true ] ifFalse: [ x <- 151 ] + ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123. +! + +METHOD BranchTest +ifLiteral |x| + true ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '1'. + + false ifFalse: [ x <- 151 ]. + [ x ] assertEq: 151 withComment: '2'. + + true ifTrue: [ x <- 123 ] + ifFalse: [ x <- 151 ]. + [ x ] assertEq: 123 withComment: '3'. + + false ifTrue: [ x <- 34 ] + ifFalse: [ x <- 521 ]. + [ x ] assertEq: 521 withComment: '4'. + + true ifFalse: [ x <- 151 ] + ifTrue: [ x <- 123 ]. + [ x ] assertEq: 123 withComment: '5'. + + false ifFalse: [ x <- 521 ] + ifTrue: [ x <- 34 ]. + [ x ] assertEq: 521 withComment: '6'. +! + +METHOD BranchTest +ifRealBooleanMethods |arr x| + Context new performTest: (Boolean methods at: #ifTrue:) + withArguments: (Array with: true with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '1'. + + Context new performTest: (Boolean methods at: #ifFalse:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 151 withComment: '2'. + + Context new performTest: (Boolean methods at: #ifTrue:ifFalse:) + withArguments: (Array with: true with: [ x <- 123 ] with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '3'. + + Context new performTest: (Boolean methods at: #ifTrue:ifFalse:) + withArguments: (Array with: false with: [ x <- 32 ] with: [ x <- 521 ] + ). + [ x ] assertEq: 521 withComment: '4'. + + Context new performTest: (Boolean methods at: #ifFalse:ifTrue:) + withArguments: (Array with: true with: [ x <- 151 ] with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '5'. + + Context new performTest: (Boolean methods at: #ifFalse:ifTrue:) + withArguments: (Array with: false with: [ x <- 521 ] with: [ x <- 34 ] + ). + [ x ] assertEq: 521 withComment: '6'. +! + +METHOD BranchTest +ifRealTrueFalseMethods |arr x| + Context new performTest: (True methods at: #ifTrue:) + withArguments: (Array with: true with: [ x <- 123 ] + ). + [ x ] assertEq: 123 withComment: '1'. + + Context new performTest: (True methods at: #ifFalse:) + withArguments: (Array with: true with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '2'. + + Context new performTest: (False methods at: #ifTrue:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 123 withComment: '3'. + + Context new performTest: (False methods at: #ifFalse:) + withArguments: (Array with: false with: [ x <- 151 ] + ). + [ x ] assertEq: 151 withComment: '4'. ! METHOD BranchTest @@ -1008,7 +1105,7 @@ init arrOfChars <- Array new: 257. 1 to: 257 do: [:idx| - arrOfChars at: idx put: (Char basicNew: idx-1) + arrOfChars at: idx put: (Char new: idx-1) ]. ! @@ -2057,7 +2154,6 @@ run4: rounds | list indices tree | METHOD Undefined main | command data x | - Char initialize. Class fillChildren. System fixMethodClasses. @@ -2072,35 +2168,40 @@ main | command data x | COMMENT -----------Boolean-------------- +METHOD MetaBoolean +new: arg + arg ifTrue: [ ^ true ]. + ^ false +! METHOD Boolean -and: aBlock - ^ self - ifTrue: [ aBlock value ] - ifFalse: [ false ] +ifTrue: aBlock + self ifTrue: [ ^ aBlock value ]. ! METHOD Boolean -or: aBlock - ^ self - ifTrue: [ true ] - ifFalse: [ aBlock value ] +ifFalse: aBlock + self ifFalse: [ ^ aBlock value ]. ! METHOD Boolean -not - ^ self - ifTrue: [ false ] - ifFalse: [ true ] +ifTrue: trueBlock ifFalse: falseBlock + self ifTrue: [ ^ trueBlock value ]. + self ifFalse:[ ^ falseBlock value ]. ! METHOD Boolean ifFalse: falseBlock ifTrue: trueBlock - ^ self ifTrue: [ trueBlock value ] ifFalse: [ falseBlock value ] + self ifTrue: [ ^ trueBlock value ]. + self ifFalse:[ ^ falseBlock value ]. ! METHOD Boolean -ifTrue: aBlock - ^ self ifTrue: [ aBlock value ] ifFalse: [ nil ] +and: aBlock + ^ self and: [ ^ Boolean new: aBlock value ] ! METHOD Boolean -ifFalse: aBlock - ^ self ifTrue: [ nil ] ifFalse: [ aBlock value ] +or: aBlock + ^ self or: [ ^ Boolean new: aBlock value ] +! +METHOD Boolean +not + ^ self not ! COMMENT -----------True-------------- METHOD MetaTrue @@ -2109,10 +2210,6 @@ new ^ true ! METHOD True -not - ^ false -! -METHOD True asString ^'true' ! @@ -2121,16 +2218,24 @@ printString ^self asString ! METHOD True -ifTrue: trueBlock ifFalse: falseBlock - ^ trueBlock value +ifTrue: aBlock + ^ aBlock value ! METHOD True -or: aBlock - ^ true +ifFalse: aBlock + ^ nil ! METHOD True and: aBlock - ^ aBlock value + ^ Boolean new: aBlock value +! +METHOD True +or: aBlock + ^ true +! +METHOD True +not + ^ false ! COMMENT -----------False-------------- METHOD MetaFalse @@ -2139,10 +2244,6 @@ new ^ false ! METHOD False -not - ^ true -! -METHOD False asString ^'false' ! @@ -2151,17 +2252,27 @@ printString ^self asString ! METHOD False -ifTrue: trueBlock ifFalse: falseBlock - ^ falseBlock value +ifTrue: aBlock + ^ nil ! METHOD False -or: aBlock - ^ aBlock value +ifFalse: aBlock + ^ aBlock value ! METHOD False and: aBlock ^ false ! +METHOD False +or: aBlock + ^ Boolean new: aBlock value +! +METHOD False +not + ^ true +! + +COMMENT -----------Thread-------------- METHOD MetaThread new: aBlock |instance| instance <- self new. @@ -2484,15 +2595,6 @@ args: argNames inst: instNames temp: tempNames ! COMMENT -----------Chars-------------- METHOD MetaChar -initialize - chars isNil ifTrue: [ - chars <- Array new: 257. - 1 to: 257 do: [:idx| - chars at: idx put: (Char basicNew: idx-1) - ] - ] -! -METHOD MetaChar basicNew: value " create and initialize a new char " ^ self in: self new at: 1 put: value @@ -2500,14 +2602,21 @@ basicNew: value METHOD MetaChar new: value " return unique Char for ASCII value (or EOF) " - (value < 257) ifTrue: [ ^ chars at: value+1 ]. - + (value < 257) ifTrue: [ + chars isNil ifTrue: [ + chars <- Array new: 257. + 1 to: 257 do: [:idx| + chars at: idx put: (Char basicNew: idx-1) + ] + ]. + ^ chars at: value+1 + ]. " otherwise build a custom Char " ^ self basicNew: value ! METHOD MetaChar newline - " return newline character " + " return newline character " ^ self new: 10 ! METHOD MetaChar diff --git a/src/TauLinker.cpp b/src/TauLinker.cpp index ec51670..6b49736 100644 --- a/src/TauLinker.cpp +++ b/src/TauLinker.cpp @@ -275,7 +275,7 @@ void TauLinker::eraseRedundantTau() { if (traces_enabled) printf("Erasing processed tau %.2u\n", processedTau->getIndex()); - assert(processedTau->getIncomingMap().empty()); + //FIXME assert(processedTau->getIncomingMap().empty()); getGraph().eraseNode(processedTau); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 98a7cde..d3d9ecb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,6 +5,9 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure) macro(cxx_test pretty_name bin_name sources libs) add_executable(${bin_name} ${sources}) target_link_libraries(${bin_name} supc++ -pthread ${libs} ${GTEST_BOTH_LIBRARIES} ${READLINE_LIBS_TO_LINK}) + if (USE_LLVM) + target_link_libraries(${bin_name} jit trampoline ${LLVM_LIBS} ${LLVM_LD_FLAGS}) + endif() set_target_properties(${bin_name} PROPERTIES COMPILE_DEFINITIONS TESTS_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/\") add_test(${pretty_name} ${bin_name}) add_dependencies(check ${bin_name}) @@ -22,3 +25,4 @@ cxx_test(StackSemantics test_stack_semantics "${CMAKE_CURRENT_SOURCE_DIR}/stack_ # TODO cxx_test(StackUnderflow test_stack_underflow "${CMAKE_CURRENT_SOURCE_DIR}/stack_underflow.cpp" "stapi") cxx_test(DecodeAllMethods test_decode_all_methods "${CMAKE_CURRENT_SOURCE_DIR}/decode_all_methods.cpp" "stapi;memory_managers;standard_set") cxx_test("VM::primitives" test_vm_primitives "${CMAKE_CURRENT_SOURCE_DIR}/vm_primitives.cpp" "memory_managers;standard_set") +cxx_test(Inference test_inference "${CMAKE_CURRENT_SOURCE_DIR}/inference.cpp" "stapi;memory_managers;standard_set") diff --git a/tests/data/Inference.image b/tests/data/Inference.image new file mode 100755 index 0000000000000000000000000000000000000000..f97bf11bfc47b655115016d6c8d7b6a2fed8cc55 GIT binary patch literal 232051 zcmc${d2pOpawk~8`*0Khk^pZ>r1Sw~H^Js10HjDY#0$J6N|bm?-Ay*xDgZ^YOaKK| z0V3I>Rl7j-?9PVNl6dIqmMqzQ$R67v(-SkhI~yKJ-QkUCPk7t*+A(80?DcwQV*YUK zMzalyv0C@Hzs!8+S5?$ucPu5czVChS$d@lOU%q_#@-<`{n&rPpsqCZqsobuyT)EuP z($I{AHcB{haeTNq79kr_=1|I`xziJcsp-+YPULI{hJPtPG8G|HLnGCQ%yTL8?!w4a zp*WE%UDUZbbysn04B7DkC9(~T(cA`x1f@Eu)Pd4yzLXywDwT4k97CrQqAo7d6fII} zB7Z(=RuO&%@!NF1Fm;9=+W5h|&2w)$si6rlE>%sgi7wUVgN4sopd{#PyJ|N5Y+?wO z0fL6DXs&T9{o9%=ADJo@CQj4yaIRb!*Rd_1DH&+8s)ZjOqXO;x zXgiZVH=QrjaB(B0e0j1sFc-;o^e}hnFy|;2h6`hbDU6`X655aC z#~8}ii^b_u887(I_za`(nP)&A%c7o!R!uxiR3L>jMSvK!GqxLn?Y2{6)8#X}awBI5 zmk&(jfXU&DQ~6!R(R_Ijt>sI(vGO2LH*%H`J~)*hpDdO@fBeSKA?*U;4wa(8=;7#q zYW+#iY+QM9_NTgeCiI=C*HGM2hM*x@6fJLP(8ZRk=D7Qh7ta1PI@;8@Ad|^7;%_E; zT-{037?;s}TXxGpUrc@F*KKWn_Y8U?shr5UsW34)O>DGWwV;x*!UT0QTgvA~>6y^4 zrgFo8y-5QxtoH4goE@rMF3tY1JF}~D_1tX#Tet4a9Q)bLyE4aS`v+#O-Pl`sg;V=7 z$8Oy0m(*8p?aK7^S6;h5IeW7^b8xor2KA$%E$UI3=!BL4t;CGG&g4p#O~0 zag8JRs9Hvg`vh{O@$B1?J+QXsQcNMq$MCfTBh2BkkM)M%|qgt z`YJOGwGuy-8=D3yd*TrwVk#elE#d-XP$4%49LKpBsES7bX57=3Jt^o$z4Accz$$Tn zOpWNeq7zL#^5;}byko{e)HsJmaCrd8{UfEeIPNLsr>092@kp*zD&$V*WAlz?b0Z+T z9%MSnBaIf(+=e#ONJ&LM$)>4fOwG|a1C4xfo6THKLzHQ#g6IjLxGf8OXc%%6@ty;F zfs)a}NRHTa-8wKl@G@1J&c`%JvSGEa+p47)Jg_HfGiV@sc$mTW}l8n;-_hph+5Hj8G>*uFXM8mR2&C< z7=gGvh416k{z$xKos5yj#QL^u??^1NYBf4oICT^q7>tkmh#iSf@Ta}tbNMz=7(<%D z_HwnQd&j%CHe?pRb!$~-Xy?q8Q?rM5zt@vl+_$Ii(9Fv-uUvZd#--P8_JR)&wN|@*b((KHW1eJ&vJP?s~XmUuqRau$VmuRHBS`>M_CLi7RS+~kdDSo zaxWv-(ytz`>4^daDi2J2s#uDLj_lgEPbf3)#XAObH6f^%3&vaGf%`VEjx9FOeIoX9 z%;G&r*B^ja7F@&_gH#%r&1U;!Kud2iK#q?O7Dg{%yzobABPKzTut_gbP3%|@AJ+BL zm}Ck2C#b%YJhWwIE(UZ>e;a@4iHlQb@}={Iavo$?7$agBnJ!O(1h`hmq^^EJkr~60 zG!wK{v*jlAjbx%w9P_45VYrwl(^THyXUQawCcZNBqKr5sx}j+_L^Qn~QWkx*EQZ(m z)qas`ZEQhFnj2IT<1=b*QW5nRH1m0$JvvES*n-!!(fVkU>L`?l#wO3?hVxT}5y;TF z`h8-0JYV9JmO^>wSZ?Ah3D`n;cj0t_rfJOTAP_uYw%=bopD*pom1&m1>*JG?_BA`^ zzC~N3?J7I%eQjcdV;GtFly& zCsU($qP1aFThmqPgj7}QRmYA)GcW!9q4?5e0ZU<=PGDkrKB(_8qjq_uKtmAcK%69R zAc!=~OnNf!3UBm3;7<-DzAc4pQc)*2>rd`y)m&1+EEa6;Us0V9njdy#;>t@m=VXox zz$7le8+|VTchDbqH($B2jG^m6=J8zOjj1Q&-m2T!uSCBZG%+; zg^3Hz;dzv(YrDrVK9nIuOo$#dZ>!!;OrjsI%yfQb=JJPergPWK)eo0tI`_=Hd~6O*k4SukI(m033=lCNvpCy2|FxsiN%Z44&Fh+^{BjE)y{B{wz} z4-=d4;0kSSjW-NvpiROTQ}6wK>4XjO3Hr|t>94BRc*l@Od*JS~jeWxAXB)qW2h{=Y zJ)h4*8)sEL!2L?ivv!P4-gspnbz?AI$&!OLkzZ3SghWd=jXdv%I9bG;Z)q>y3SsW1 z?eEqEH)p{8I^q1s0f4=p0I30FP6PRMi;?fuY+}eXaja$&Rw&hIVn{b}%r^0TRz7Ym z$P9I2>fU`F>MLmy1NFK+f|V5@5C{3DfC-p;BnULZkr+aQv~DRA3W*N(#^wRT>*^7n`1OIKw` zp|6wZViHVMY@%_~CsN%i|iSrxeeb#pAxT1lmdJ#`iB zNKWWft5&V2?0BM?&`2TZLdT){npCbIX))4L3L1u#b=CFw^vqVO>O_4043^=n=A!S% zbyrcBG`a>k&FmJF)}<$3%Pw`+peJv)26b9i>yFAx6>n}m3)E?J0b7KwZmVTEj$srT zs4OI-s=51*KB}59PpTDm^%_rN88A5|)V*ckJ}k*N!7)!>RfCjIfpiC}W*QvQ%Y0B8 zn*n!IlWG>;fwI-Zy4PapUNFx*Hci$Mq^@UUjkaw?FQ^VMRpHdoa5+CgE79nAZNM2~ zBTEM>s(3bP|1kP=mR94fBVx*;=V+ujNyd%{%NbJCqWmZ+Y*6)Du{6tHV53a*eW+q$ zUingCRE$+ErTkMc_po&ctP9H+f$#%Zvx=or7*-kQx12o5~hA*JTcX;xOo>Pj~EW#ILw1`fqcMWpl zr-}B2Cnj@%3d#YxpI?UwQx+;vP=Um?X7x!VU7Uzd!JagY=~x(s*r5;f%0gI{8zZ#X zAf6EYp>WEq;7+0jUQtlHC`a-*{zBZ}N3sJ8LScn<@y7eH0AVITilw{tDvGP9?u1%k zse(i%FU)O`BvLIqVsEkqZhWbCLM=yWCJy2_c@ljwkgPALd2#o9ZJD?m3yDi^?j{S# z=sp^98pp)fA5W-lB)*v*P&cE;CSWP2DbT2YwjL?riP5A<-9hbQnf?}*={*&!(wAg* z_RU<8`6j~HCm3=FLESYHkr?RLYBiU?Fp{4n%fZgE;>cN$059d;0^QKRfPrzvu&{8$ zh!B3=A`~(%RhbMQmW?{28TMcfrd7R>OV6uCad%JU#Y-d5RVQ??g#U zl&tVHnG>quI>lC=+!JCl>SY9n(90AlIfS;bwkAWV>jVf@7u3UO2FwAOz$^#r-=bMD zjD%ynDr}Lgfc?fS1*+Iaf*eF|dz`eWww}2APZrm#?f$Sf-;RZf2Q+^$z)V-w)wimL zcbp?5A!Nq~{h988nJc%x01H&ge(uK1YqR~8 z*ALEo`_^vC@SU4`NYDsz&CymuG4!*QaDSmpvQ8}X5$vSPqPwGeqPPKs$O`T<)fHDh z$^e4C!v~*z$yCK=!03XECX;A#znZ|T10pO;kO_Pw4`IYKW?ms9%F|fs5=*YQx_Z^h&OT$#0l_8bf=6Qfj&yRhv7 zv$yQSz$CN>+7xLVt=LDJ(z#nFi<5hGHBckL8pGSQw)l=OPbCdCG%99)olksV6nN4U>d#aF!Q4@*|?1N;Ez!uZg#hCNjPan4pVc9)7rN2bu( zKAL%NX0grYDKY5nGrk-@tNtA+$~mKgF+Iu5$B%lLWWqbnNwQTEEFxLS!av7NMVeAL zKR>S*N=0>22Vv(#8*Hb#WkXt|L1A1y>^TLAR$=Xp^HBr@~AQ>%?mY3c1h<~e~puEUz z76qYVy&i+W%t~$f)dfft;e6I~)Ofi#Pl+CSmN$pUvvg zT9Y&h6mLS8y~B7=0|G7GOz*4?v0jUg__TIJ-P!pTn3ME|n~O4F)tA!qJZrT_LL9wS zEtpvd7!=5&jgtVP6YB;Z4|ykCE-Tb+HPy|m7o7UICF2`m;-ts+7Q;u?Zgg;A2K!!5 z&URNs>?{IPbz(B;&OrLVb)&Cx<*g6<@O;%cTy$5iVcFHyU3mp6kC-s9Py+LPB}#@t zHRgZ3egi4tP$fG(rIX|#Ej%ppW!C7mU6iG|!;wLUDZpeWl~~17`b&K_geDp2&3zNY z3grTVxLQLPz@BMevL82Jm|YkoJ1$}dHDhBkx+<9Z^$?h^R1L~)W+O8IW!EFN_lQ{j zryL6hXxe8ec^rOU*s3$LrCPRc0__(@A-KVXY=VhUOoPa^$c=b;LU$WUlYLj7tX1gcJ6_0GQSig{&{)0O+{cqpsfBV)08F(TQCm)=-e5*gxf8*u=KEr~4 zZuaJ#SS9pSyj{b0qUW$zj`=_%tl(ilNvdlre;_~HaDEvUh(o>w z;)RgE7V>1^sDYNSuJl)GZ9f8=OYIaQ^Sr^5D|t+ff);PhB|Qel_IWbD|L6|A*vrv8 ze{Qy?yZ_RaTiY|c`}-=_F1=iWNo?TKD==x?+<^jpl~>EN8{WG0l}z8h?qio;pZPX@ zIfgGApcdSr7l?2`i<0A{6TPE4ShUIRDBEPP=!I%6}sr{b9 zZ6FZsLLdf7IvA)d{c-SFK%|VWSffHR%fBZD-~beZGRnbNH(jR}*D8NX&9Ff|7Sh4E z*YA(Ukp`_Ww%XN6O=^%_qEpGZMnK~L@peCstCp^VJ`?($DqyFDnqHUe)p`yH>DCmd>=;N!D5bfco>2For|s|xdl9Zs zofQcE&dO!7bIx4Jna5sE^*(6GqCV&WAKc_fyp-lFhpU+VZWuZT2dgtGDv|g2T`S+wW<&oG) zH3z$^Sh!;PykxblDET`Wa6*ZHpoZauWmAoG&zzz)@oJ>lh27WG=P-J6X$B`t(h8^$ z?~&>aD=k!UJB(ThBgLDHOM1wRQt=JGQ;XmcLk<>mY0vZWjc_+B*{nCTwAyH-(5SYJfhSq` zS8W3F!T_J3?QkDRYdr4TP)sp43u!#)6_fttL`tr{I-3jmVt);r-6zs&tIGLE>Qu2t z(jA#@AnDc;!qpW>>ti=&t{Ry}{%(RV*txM~sX;R+djohNKbLq42aAlw+h8HwK}{pi z1MawIhU#@Su_%!(Rwy;A11>UcgqW^}>*1P)?9FN(2{9(xllnB*_4>ui6Qf{P*H#1mO)&oZM6qKL4OG z_6p*EX}_l@=1j)V8UAra$+0$`rXv`BVQlzNVbb~G%O)?{*={rVmiq4ZC7!8oOVS)+esI8 z!(U*Z*QaS|vQ{aWh47(4b+B)lobUrzG>9unJ0CbJ!pD=g2=!iXCR)@$hjFxPUkg?; z`nzl`w$PchT=!&9Afa zA~cPil`FGd@2|N58rRYoY>fu!eTi6zw&BZaAt#a105yT%qedi6 zZGcp7FuUXt84DbX8yk}iWy%HlLe77Jc@!Ma!_yOz32Cc(SJ`LVb$uWy$JO_8Lo7fX zf38{^>vF>2MBO+$Ypcp~A+$@s<{%ObQFK4(7iyXifUgD8 zVHsEb>;`oW{IIbp`J(t?^~I!H_;#KoS?_bf!BP>29zf1@xWIiEE`ZOKYSF_1l7?d{ za|w}m+h}pmh5X3$6nlcS2-j4*g6{>Y)L*m53KgyuU|wSq}rmS-mAJs5f}V7)+Zz@nc1dFcbkgBP)(5g)$y+ z)}GGGiXV&PIr!|dwU-@PdZiTmd#u)61&-65bu0^QfUDOTeI|q**zhPI%&w}54O3GL z@8R@0%$_Gxsc$T|}%`7%Y+ z&CU`D*})B(m9+xKm00$ns*v@h)UD~msUykSPb^jD0qz{la87b0$<782z7Wo^?de=Y zoQqDs12J^WLQ9QD)ro%uvmIwlV&Niz?a0mFyD3eoOy}*bgi>0$#1b zgz_aA?$fZKekAhooS6vwDX4TI;vA?5LH~q*V(Qp5x8u^zWw!iA7Z1 zI$8N((S^fJ^#s)K_&RJ|spEu{0s524DwCUq6GWCLAw+Bi8B)^Ae5)(O)~RSQFQ>wp z{~om>rICps4771i7Oq#z#jWSy=uow+v5Ix!s~>`_?=eu)nP z^=dg1<^*9vDA8M4B~b0&`xt3%m0eP9WP*Uyc9Sh7BY{Gd94yiqHkKoNLWV%0S-?B7 zFS;Ymf48pL^dJg)H;3@}$fK7siK6IWlN8#Izu2^ptzi{X zo9zW??V@K0)~!Eq+)OJLu#!|Us?`m+<#2f!_j&`1{&*{i4%HO@Q6a7Io={zMa11(b zf8Tu!qSJ3Tsrz6KhjM%P-CHYo6ZzJ%40c1I;@-gi!m-(dH*T)P50b7{uEFIC3h#9> zRIqE&FsrfeK$Ydc-lTUzU895T7`^h>N%h3Pz@P{R_V|J3hopGG)6SsfQQ}q_CLdKY zn0oxQT^}S2C8p18?bgVGvtHaBl`DUJwV0qCch6Cecf;%A!%o_N{iyNA%`Wt_8$42Y(5v z*5n0WPd@SzG)V-H<__3!8)L=dSq>?JNKDc=j6qXq8EvqtNr3XekVv5srUbIs;a%c` z$RMy0591H_4h$~at4+L+9-My7=Oz&jBM-qv@k?|p9;Bdi`E%27hJimknfo+fQ;To{ zAZ8x%NCg>84n{MFAE6i}>@fiLjIqdBURtW?$LX# zw5bTEM`x=0K8$q%)%Up*#cu(XGqZrsLE$R_fqVQqYh%s$9~I8u1!NM8-K82uI{%99|1%{`!I%(a-9 z4{<~FR-D7RrqfW0ZOfMcpAqx^(vRTLDsdaB-m+E;VDJJ-JC?48%La! z!8nG8r;|Ux37V84xL0ph7)pRmm$x%?at#x2XD$13XR6xmz!&eHU~vysLd?oSzS^=} zBSQ^?@(*e=$q%ZS#03Gk4U^bRj!BZPR3smvyrfk~kGBF(sJq0*&Q&ud=xq-S0P8K3 zt*OZ#EXn$T+2nXPVy9svMNVGf*sG0>wjREda4VP{>Yu?0j6?k&+?DAY!0d3Pa`n=+ z8<&EU(vbQTTjm9FGNbeTV4vp$iGNgm!yGE6X>zJmcdDdspv)r@dIlN;&1j%bk+7ID zwLq0xSUT#kOKu7*(0CCXt3|k>PD_6;s5|-WDQ3bKKU}~LNgO02u3Tm<7YAfihuN!n zB!7`+FE~(<*O1akgQ~|`VL)^rINl6B)h9@h3fIRU-4O-}NbQyanI66z) z>OX~{nzl0h6B$RqbB@5@%d*zYR2!ERz3Ssi||D#~6x?Sula$u(Rw(evEOpf-N0` zn9ex>WDFIXH|!r3dmg4aaH@XMGnd&D)v6aCeA79ICiKxbI{s#~vvd}HLT>mrSvCmo z(;h{=S~f}+IMUV4a-U?Gx?P@4l4s;L)oj+)APXJS()eI8Y(i@Y0w7Y{_S-zHbjyQu zpqc}2(xwBib$OI%1!+c!1W~dHe}5T&D=LzWJa6CZKh;vJ9#$u~0mOSc{TOzW5c-aq z;(z6x2AiA-#wYomwBm=1E?h#u)zT<$g>y^?L_g$stE8cZM*))L&ueYlJ*<{kk|VUZ z7q@-!7b==xWx{w^EixUaa`xF3s~KeMVH}i0FL?y$*Jb}@6)mXI$`VWXRK~ryfoD0EXqf*TfByo1dAPA#k|ckrS`g0b zEJA3KxqWT=IPp4fj_Os5aLlfBnma|iAY0|tfVGadj2hAlXey@5ZP8&DP>EYauMeHd zqwPVH6#9ebZLIRexysk*P1EaO6Q}e0MzME}Roj;&esX}3Z|I1{64sGe%apdbB%Pu9FfYsIdh$An@w^>iiwd{m9>czaxr` z*hj$#3|)sdP`Pq_H?702<9rrjj!b6TgRQ>-yf0Zj2tBT9$B))n+k-P7z3OsxSm!u* zB#&J$9z%WTLbL{t+IS6Ow&2!asUkT%gS}8H;^g4tsygt{>WCVXtu|f4;^;m#AKqN# zWl*__W$3lppDf@$!%(W4_8dNZ;4u2L4S%tF5^W3UqNw2}%@jnzfX8&m90IybA#yuvxWr_x$nF+c5Dj(Yble8CIVpybYI%=MiOzW;70V@>C81hKCV3_o)KHGE!i0 z9q(+*z(!Mn)ijxY>dAon)mm1}-f!1u6cFkKXBS{HJTBHkoLGRHbvxXwzsi#X?~D#a z>r@*nB1~xJtPSsk_Nv#Nl`6N3cEX`UQ9iN~@UPLFxG zyshq=5jlD>FY0kHfa*Zi{Of?zK3bXho0w_NL~vtU`xZ<}FmE#N!P#AP9yF^b)XS0D z!-HhKsxLk4fiPK>F7M80ZRIdGh+EYMCpPb>-F9{d5>4QibzzDA;B>8a!K0#cMYr$g6m1$7E2_B$?hW5 zN{0f#_(7GQ<0=J83@O)3M5A4W^asPXJJh{6w5vUG-o6Fvz@hgSWp>@TbanFPGAs@I z5W@6B_+C9Y^XkpDCW0vE}qG5Jyo1h6lp@r1h#*6g4mLI>uj{!1&!$#3(bqS0E z-@x;jFTpybKS^J~GCJ8=5KK(LUj0RAh%BOxUtA&mBnuShYJNOkIYGXZtl8^4*j%}& z!=Sp9uQ>b75RYW#C4}Da1cYjXx0kE}$UiAbI)Wh>f;WY^Ab>W*crr+_m#YARPy5EY zyk>ZIrZlyIpq-=o<^d9-J1;_1p31?aWG%(EE#=pVJVlInNO^I9dRUN^xX#Jkb>UX%MH?OxecawCb+{vzHq8vRi`$`q=+`u9s37t%n?}7 z6)BisAQkBdG%Bl3ie)Z5Aw}eGyR@g)(yCW2FxN5gwpO?x+Dk#sW&(zT4=2da%nBNs zyAaM&E9#=7@GBphvCThNUDrC$PLdA>b=fn|q-qMQGIk4cTOr5K#x1aUV`~O}jrjXk z^kWr`=P2$Telg?1$<6s>CgZBLJAT(YP?~Q4^Xl#`(i3e>MW?XoMQ98=vlh|JfEa|Q ziMxO2=G=^)Kgp5>D7+qhUabo%WPt=5Ix2>NkAeUn=Ok&?aUS_;HUn5@JAVrKyjrr2 zF{H=a0%#Ep03as2Kc=4ogN(V}c|(YAf(pZH>-w?lXV1s<^8p*)7>JjyVI*_|rrY+D z?KSfFy-p@DJ9WQ{!(rV}OsXS0eqL>%@%1nu=%WO06O!Ie`1@y^D`==_8j8`7gq#ZT zyoCtp;~J8B{g$|CLZDBoOJSgX6RX|k+{DELe5prEuFU&@BtZYb7_Z?;PNxC>4w1lU z-;ssu6;MnT0;A;PDmzi!BT+*@>~o4EIJuZdu)MN_8QrcB{EP17;oP0H z=@XSv7^NIBQ8O)kYaqE#eskgN3Q5 zu^U$4O)17q>fDm$Ff{eN>fV7Cc#8rq_&6rs*FSLS%5483io+Njsi8eT3V?41gZH9W zs&_OsWyPc^JIuUFAngK+ElGh&ArDA_jTBl5F$_MtQhkwHZ1D-vYr_h4SB*NEWYtp89G&m$LIcb zqIY`szKt_i4qY$J4jg*-*2>IogvPyrTPu#u?u9cE_Kwe$XXD>Lo$22QzoMZ`|9!u6 zb7$tOx1P-OZP;|_wb>1at|PF+z|8BnzDhiC0zO7Llobj7ZL`J2fgO@$LOPQa;bugR zbmC0+7D;u|V)f}#^45!1s^nT#htT1N3a5>G21VCkcO9}|k*7+0hCd=K51v7Yf*&{n zL2V-G&YvZnt6??Ap9{D)Wa#Cb2*pr5PocP}Y_EJvdwhA5N*_!7l6Zf3=~NIEFp#KQ z2MGyh8|Dsh5xj!vJ5*F|TGV7Nv6VY_BIQ3qt&w=)%6W^HLDysIZ2Sex#(ZAo4i&gKEagla`0atvu8#0@4_;I`Jc|tyBm-Y}dTJf6Q8P z?OE3eznp9}`_fNLWQ1{94TL5raMBBE1^9>p5Zt)g0fLkm(C;sUhS7bgO86n>2p@4i zW&zg1@85dbCvIV*Ems&P$pwcGap-ts+=1#qA-tpnhzz4H0Sa*Pkzn^bf5Fu(?GDBl z!U!e?1Vnb$6>3Y;`W`}tU_-vRp@PZ`TpaZ6f)FZe1Cg@L#;t%+fu$N|8!`#gMKkz2 zQM4Nvo^hC&B8YH-xi@7%Sg=Qwby{A*(vzo~2C4)p?rqcAQucxxg`KSc0Bymbh zs~(jD8sMS=wyp$KD3CyV38J0KR|`-(R=w=ERrgX4A$8!Q5%T>mWommgcj7CszN)h1*uenm={HPL?aFi zJ6M$3D{m0J1F3*b21LRh+AvO+I@yl_mL!+4R(lVtYOE;P#_WuT6yz5+AH*YG(cXpq@1k{~0I`oHS7q$A z2A^~NB2VvU8tj0-Dz)^?n zJR(#8Ps+UYN+-Jcb=x*mOxMbtB1vdxaITKuQq^ITAeXArByHOUl9ihn1VMgXyOm;o5oLSx;wT^pl~hst-~XCu2)pkDb>yUC^kGT)Q7s zma`H+6PuE&Aer3npfjL{$dqZlZ67CxNxs#9IHNsEzKq{1xb_O?(0VwOx{0mR1| z%yZ;wFPWW_PTlOBagA4K4cE`!Kd_W@13VX)*6eifrrPY#eu!c#eXyR6>0H5GG&r1h zYc0LJjF(s5{n^cRbUFvYEiiGgU4u4UWekA+A_(sVT<%8y`sZ5KWM|U_{6Q(}7qkE6~Wu{HG;k&TP@V>fFCRnX8(Dz?7=F%~i7v}iP z9M2o%t;MFr)bhDUtsB;75i}8N1LUe&g}vqT3PhmRA(g>Sm8;kHc%FZ1S*_AIqQ+wWk^M6 zPQ_HF+3b0l%xIp6>(QcAG-VMB){lwi z!T|?4V=K6cErWdyRfNMAzb0KACgRvY@mviS_w!XEPYr}F7r#88s)vgAr9<|DlYPTIM z!FdysreQmY(4_4qS4<;RlBJUDH9De@CQ#A=EzlDLvLZ!UJ9}=@3J9H?{v4LXaD%*C zpPcTyeuG15;q3J1X(4&rv(uY^V*V>EqY3<#U(DHQt`%(kb?y+>*tO43>uerwaD18) zZSO%J|Dbw~VH*QX^Zs3#xPOLxx^5xD+a$$zBNo=?0n5c!Mc`$6kNW2+bzbXxkYhNX z&&#s{jj;~rUPCFM6+PL~^$OU5i$pr)EBd2vPvi=z2(8uV@5zGKr60oQmim+0CGzjM z?opeJshy6&X~_#jiuAS~EB)dXVCAQ4WsbUsrQK4}u^4BIX@h5p;7rGow>Vpu5_}Y* zo!34FS@opf=mj?LrEyXVDFB`^QsdAwhWY283SK+LnvUTKZ5L*r5#$=fczcMGj9Q0L zgs%hHaIi9fh4)Yo4))W6tgom0(lz{h=z4$L^Ud^mO@y5+swwGGCzws)hVvflm)dNb+C|%p)k-QiI<8vDwWxZ^5ODK^)m!H5OW??REWn z$wW=~WdZ=53g%^DwSkNlLG`9$M7}cy4HEVmDx2n(cs7Y;rYx8(YS0{?{Yp*zpsohh zOG7myYv=wMSle)G9Ry~K6*G3!gilN~+bM`|1luPptxsE8{=?{On5Xpij;o?18iW`ztW3bYEZ5zw-Lb zWo#{8a;J`-1Cx4+iDu5f|LpLfij_iIeB8shbzI7M<3w~=zkyT70j z^MS9a=;KjDXe{8+Dln{yn7|h-4~RoecnSgm!Sbf3XuXT0{yZpza@JUMF4M?r*bO{2 zc9;$`MWi?TPKQ0}UQ)-{R)nk45!NA=+qNl;t5ItCK#V>H<|Zn#iO->JHAF{g2yQS! zTmo&j4)y%aVrJBOImrzhm{x}=@H5bH^iUw&tq<3e)INkkCt1)RKMPF)uiY4btx<3& zXy1OO!O(Q@KMKWyfA-<@q3oE5SZ2+XOgAK(4NudHCl779CyY5i)IdL*9vwC!dSnTsHqeIqQn-Gk$DhdIoqw0yYdQgCM}xhRGZ{E`oWd)|2)Xa?#- zV}R7hs|m=RRY1mSS$w1l$RjI4WtTxs0sFiIu^rv}{gN1p|!{_OJmqk|u_ z4L%hQx#K_y(MI#5%rkHemWUXN4E5Nff`1D9fM9r*TZ8F@7ZWS)+-;A$$wCyU&yW<~ zWQ8i@nFJQ8Exa4TN|YtxYSl`#SKFExjeMW04h#rdgrI=T_?=rzGh{)46CQ59(e5&= zNed8UsMO9Gteq}W-!Qto_F=>b7{!OE**+VRPC>%>>~*=eIt|ab;%hq>?37m6oBXRi z;Y|Rl2M)pELiE-j!krMtYW%%*AD*rsp21!u4BD6BuY7N2fLt;9GXuEXsmtAHf`Ndw zL`CPOIk?mdf^YUiSfu_c1v+|4D{{R6AEn;XJ32}n%gLtGMrWRW$pVO!=h;Z6lVOmL-(UQrJ z{>GVE^P`%rECn?suV~NIzMZ>h;@q}63%oQvlph-_V(+W@OU2UI zXgAD_&ntDRNJ+R;R6^ElH^+3DYv!q6CS*e2e_EV-@T9FV1O%i)u5^+5Li>Qm*s_A# zk%@1WG{u4X>~}ONUvbX8#Qs*j^v$z6>L9{&n3qOd$Bt-oZ9-DX4eRqnQt4~cv%>{~ zXt!KIPCM=$bl4#}o4-h*-P`GR$ef%|E%eHHah-G#OMKM2oo+FwYtQjY`-cnMn>`E% z+L{*#Lf%-x6!uftF~ONip<>kaj+qx&^Pa0Vu^hx6M!8%Z(F0EeqElj&61R3Va)9@)=wK<_5CB7)E7EM&UR`!+iB(Ot(BQB>=xa) z*^7&8u8Fe3o1C81h2+)oc$kLfsz0l4JGBK-UlgMYWk?D=1yPT{GQEKu<}4(*tQ7S&ix?l5R{OZ(f*VchvD#2HM)I5B4NW)8J{Qnb}V zng>$~f7}e@B>$RepPKTKPLoDf*NeRR$>iqOAiuWJ@&`K*gtK?ll0&%q;JpQzZcyz@ zAlB4!Qg2}YR_vB3vMA(fmjR~{1=WeA*`~PI zYDT9nh)8zAq@Y8pAw$rGe~FzfqCD(u5#@dS&KANsQoERd>}c18CbhZ6@CsphbOrMN z&6MA84|1}zqTHo}qm`p>-llk;)03%v(7Eb3}WSvB@#A3?`|4zW%5I1S`jB* z@S9 zjTWcZ)Sf+SW=&1bw^ME!NvzT;im~?Vs#)eD1m40~u|?4ZIx)tR#s$@~qPyo$n$iZS zcJ(T)YoObc8#aq8f?EVCSH`AF4j~nf=B9Gy-jNAihAtsPSe?;n@^!PoZv z8oS;B#vZzB?QEINs&O76D&W^BjUvp5!5}>31zjTqFb|i)mF%F}BElQrm)sZ+X(tv|KqTcRSi*ecLoXigF zIXwx38fKQuvl706jhk|j97FL~ag?BFXDCSEEw=)1IS7f2ciqo=v4jwP)RO|lyUBg( z7_+LAb)`HDFY>#BZ@+j=J!$_oIm5`dP8Y#dC zEFi#zLBu=2fyKkRPM8<(%*=lqcg)X+?159l5Rfr^b4{j;u29Ab;Hx`@K=!(x#3*&{eRd)h25@7y@$>HpyFvOaF1G=CK08Ir&R;&*j_} z!FLBxs98wfaY zY?ko7n(*BNeD}a^4u2s!_I~pLLilDNyayYfcd$hly2WmhD z8s(fTJx-R4T;pF;N15(pBK5m6^XWe4`IXDVbOrYEOxKN@-I^D0WgcAzO1yAo_~sy! z{msL~3y)>GD%UD6_sqPKLqbpGRp|gZ@`D{9&>+^o7QI6;YDjP24=GK!|6SaaYmA#n zs>Lt=UP^olo(WJ1A=ARC)EN_%sf!ci#Z~(E?mUu3~NtTll#}aA;3Nu zNfSF4IN4VNco{TvbgpV0B{=u2F*(%(0`HK?l_mfdCjj;yk}xbzK7lm(Z}epkzLiLt z`UD~YBvxCjc-{773a8c-Deu&>=xKR3kLi;{l{}DMLK~4B4ip8cR&1m+G1L^bnN)}L zxU5psX^o*MS_i|jeM-UQ6tYUgUs8dfWIV>`V&v2|F@;V?Kjb4fq98&+z$ouU+Y!lB1NBGF6j9_M8;}LWZfUqlEre3q0Z9jWJs&zL zX$KYrn*6KL^)Ehd3WO_0Z3NbvgG1b5;-TUn*NWebPY@r6(6@>%p~KYaFw5p!joJUM zPM{pvbD;&3@~^jF`4M!$zxV`NXI+`-*WT>z3Nh|KNsJubiW{9aRUK-|_QMZ1rF*bk zAAm4`)ebm?$UE~oNj%VG$=U{y9M92JI>jor(Q&07*J|#k;d0shf%&rPlBS5h=4IeL zVTp@o)4!{xu0uS51x*DL;YlVA&cssN+qLIGNJaGVfjO?4I^SaIu0hp{z3l~$d@ZMZ zO)UVYv=gWB;th=Qr0okBwB09NX;IL_52%v?1nJ8Ml(gooPn zS(V#I#it0OVEM1E9C;ZS;7g8dG=d#>=@D(GqDob579dJ zxYj*BXCGB21a7ep=^^+n+V&gn4Ra>RxcwNt>1Ax(vJN@%$9+cBs^IM{_okYMCMWX~ zqg=$6Q0?qMf!Mq-1ABc|ZAi!UUa4-XEN}fCWv3v&WtH8Dke6r1b0yqb=|l8w+Z4v- z-`E$`va?tm%jbeNTXz;mFIKh4!y&0bi)}kgh|+$>z~ZWI-<8AlexvzxO|82SNZ|+~ zCZ)0FA*p2_ZUjY?cOMl^tOR)}Okv-%4dV54?!`T-EYnsk0O3)L7)CX}@D=UVUt0D| zjKClh0M@wYP=IWu_U_#mrnH+&9ggJ5jmr{Qv=@=gk=3Qld2qUL&_p^97^3;XT>_8G z-0+}ZSqD{i0K~O-EO*-f&`KX3W!s-e6N;1J2u}R!r>h6k9m+mboRaX?UbT$=w}O(b z`w%%JJ>E_G#^r7kzm0Y>iq@dLET_ZA1N3YA8Le`U$z$I0KeNvYO(O;uCbIyW>fu5l zjZ_N~^r*VB4Onh_L!kCc|GlL4{S@IVHOD+RNtrjpkE#o}aDa&(7lvXlB*M~EpP zheL#?TbuJ}?ld-xrbjKw`Y7e=%Th zF_aGYBocW-^a+O!hOeF=;f~c2CJaO!fufk0YDkT4+i*1BJ}L&gj;Ih2>WGV|(^aA~ z){!1e^;Uv-cy1wFrlm%--_{$oxyR&-_E~Xrbz}=${Hx?k7qwJZ#{ z3}bDvAfIZ92E`0O$d_F@Px#3g*O3T3npIN4(FgZ|s+G?9ec+*fYOn;BEB!>j>o93ancTxL&)q7>rm=kSN0BM(dS>kaG zci~{JgjhLVeiENkxr6pfEn8MC_C6fOwY&u^I$+A6>Ca{2k&sHY_;TME-F9|Pi)S9* zgSn|QbXQqeX;{z*T3d0{>K?yl9~F~jN@HN~4zr6u6?lx-Be;fmbb5?X;&+RIJ2DQ{ z3mgzkcg)=8OVudOC!7)cO8kH-(1mh^YO6j9H93u6F&>U$DP1hVr#X#49?v5e%V;YE zoP~qTWeFaD^NA+N~LJFtq#3r z16uX_PL@_)IBrxO23gXM`_OJa42{xis>X-ig{NwNfG+Hz-ZilwdYAuUj%i{K-Qi6% zS*w{xr$eRF0(NA2IHcpbw_~KXI!vUpHAN-72GU{@7<21iHK#70!PBLh$C_*Lm($i% zWX9P;L6hSvhyGRoHbH^Cg1%!oy;-GYoVg}IcI(KQ!q_OTZ16?^rFvb>oyJdQzjmJY za;jE*FgDk$5APa-q$#leuZ$pPB9rh}y-uCXXnO|C!(3eX7KHr%H+7q!lBqANG? z_{7->T=jE=4M|0sB3!A6qDuQIguUmy49!w)03TvLdO2tec2TI@AP;UwDxDS)kgoqf z`KUpA|J;Iump^we>)m6L{GNSL*<7h~;1p)GfRfN2Qt7JbAh`=*h?^BIWYX59RQt*E zuI>)hlDnw2JnE3^Xp|_M50{nLXN5aVb{9^k)|}Z~+3K=>Ex=3J2$%q2*d4J|!uia- zs6{Z6O@m+>fm$$SSxmS z$PGT#I+72Ayo;n)*m_m-FqFi{Cj&sEpTx5)$4PgjTgup#{5D%wcBbw#2dyJxjPT)uY| z;dMGOZBEE~xmEk{^w`${PPt7TIa4@A z#OyT~B~CL|(XiN-0lxz}dfjzDOCegxvKX-a97-Jo@AF?Dx+eY-V34cO8NF6io2i7P zuxqi)BzyJy#aih*m_O_+Lqw?nDy*-=)5EOT(NZD}8zCnu)( zJ}}pdxg%n!9pq6AI>;S>ViE$w9iY_O9nfFGGTZ_4rFIAWueJlhU?<7L7VK!iQGw#C zHp>qcXd>{En?}ZpWq&cLGt#8#XN;03d{oUH5mOIIM!4_!BaTmFvxRUcm*f(?nIsqHRJgAWI z5x-cQ_j1lX1CLZil`8ocwh>h1Z7wBC+=mVJ5!^eCFec+iFfn*AsCL{C27PmiGzFg; z)I9yc>6v~fl^RVgEE-2~;MhmQM%y;vb7nj>ceKcghtq~!Un9RGC9gKMV6;e@-Vo2A z`zEyNVw+HHqeWDB2G`~W1K%-Pq}_nQaajI|5`#XpBl@HCGEq{G#l9i5&WD;QPY zg9x3%kvNyJ`83*|I^*NE`4mWQ-)=vt6$h`_aAn~ptx zAE0v3xwiZTx=^AVZl*=|xrWg0qqZ)!G*7$WptB*PA$sKPvdu%aV9}iyr}DdiQxY|< zMui^d$42{o*t4f{BfLG}qgb_>RGPhA?d=X|87=pziL3TA&!IbT?V8>H!fh6D8;Pt} zJpkLe7Bflvpl45E#YS6?!Nl2tAspLPy(m+yG`0s2<;QQgMJHCJ?c+-)%{qzD1j4`~ z&j(M_^;Ta0I68y zU;#FX3H(<*2r1!P-n*JZr%Fim#Y%dT;kAWT`)O>6PLJW*>M5L=cj?jT;uHjhOHc@O z?Z0=Nx;U0UdT}!UXs+yUfPrMp0HvDhz{TAU!9Q~lQ6z%lXvGI2(p3I-B<+^M%oS!M=2O1k|rU2L=-r~$5eDC zv<0eW9Jv||_fjL@;2@v5C9l_r6oPHN(#V-$oSLZeR92>l-*tUz;!{^XNOKCb&*`jX zE^pfzvwa%ySL>O=lwK{{ywuD;*f-Tg{T4_(s9OrBYRS^j&1*J@m(0z#T+Qnlx4IFJhqrw-?HV|wRTNGnv?&^h+#_rY8& zr6`@DbLBj&pkXd(+7F;==D`UG4afbAREjp+M>RJX$7(Y0CdgKE<~-;m#_)$B_Gnie z8mB+{xGdW=t|B@H==T?g^f>CI--RgUQ+^`Lan-J9pk50b>V2R!r|T^^2jqw(m&VMM@!ya6FSxbK%dAuZC%io=icuhy zbR_6{G-z9l+=gj%zfDzLrq~k@)MS_NheypZUwQgSL3`ZQ(ovXrs(4n+NuU+?0jReh zymam=hR*d*&6AwoZ0>zrBp+{MxQl+(HX8kkD;arCN736Z9VbA`T6%auM89S$!DSRM zVwJGQDV87>15jHF1}r#|VcZ8GO(6tE*6k>=~^CW$?8c#`!^E3!$qWtskMV zNkPv5=i?KF3n83n=}S-z>cio_YB*dgS(L>96wfn$TspvWr4Cr|JR~pprNY$V>4`yL ze`2(Z#oUFesT-%INZh$v+bD%pnF)5|Y zOkTq+B%QD!=aU({?rC?%%hfiPzhEt^u7Or04~-#AWRN+F)SVnkiAKY#B^rYdkKa(M zZakbVR6FIfGmmR-E~O8Rjn(+nM4wVKh?+AdzC^VYaqNrGhI}B26jVDOJOnx`PS(;J zl5cvwnnSq)&mHe$DE<`kr&4VJ%9&;p$c`6A(}P^8sCe4CUlA4r3m&0e_@I8ciYMowN?&?YK{P4 zg}o0laW}GasMG*LHIL02*4r(RyJ*CkJZ#f|W3B(P#=fW~0vY2Hu%DVdQ$Ui<6(uL^ zGf-Kagk_62kbQVk6feE)TLNw#5j&`_K=1fkPv2Q9iY=0P{ zWUecrI=RU&6-I$+3XUBDl%+~ELS5C(Tn3tPFfdJsA~zC3OCln<>{^SQBBfekDH%O5 zF&5Y(=9F^hSrhl2co^|KE<7qUhPzYxM(=Cp@} zx3cqda1U7u`!#k^IU*tUblVR?^1sdrzE*uXXk7MF!_R|pG+zodS%l-HmMD-;Fqo3g z!A-atZ=8X?;c2+X7#~+R-1GGp%{ix7X8XMb2<+4vH>A^K9T%l%Z+ecoM zoOH!;YRD&VYUYRF=lnyK?UoLzRQuA1Dotjvkpl1#2^R!NM2~*+&Ib z!E58G{3sDI$tL%y6Q7D>IQXXRyH$87=V`*V1-LJsE)^y7>^F5+iTy(N;ug(|N6!~` zibh*woq-TC9t^O@j%J!vbzNif#AT|yHQ!W|EV zl%Q3Jv+0Qq!MJnMMnQ`QO*=9In!RL%4-ndl4@ZCiq`IbcZZd;~=x;&p17bPyDqpDU zu*)Z4lp%06w#r#Na-p(`h{_XU(0MH>9ORtfgRXJU@Hahxf zhS4QQlpNxZz&dpVl?FJO?>93{o*IHrR~5jU+DqG40ou6S(ISKGbhn@sG@cynL?IM& zP@9<}$fDp%%*7WHu}6!8BBuRZcaqae*cbO@h%oNzYb&2EOzs@ZO`HwMt)0?o-K)LM zJ(}w>?XVZjehx6;eV!kDK1>lg{2?ZG^)|E?mW!u8#@N$JS6L84_;3Rcr727G{wPob zR|CAanF@%feY)T2@GvjLD`#|_LY@Du&5M^`wlAuyOooU9dSM`7x`L{mKO2q+z>4sR zP~&o==4Wlq)Q==_r)d=O=L2hU8&$w6Kzi(KI`zZWsFu3|uvloZk7)Wfe%Bc#K?n^1f!oGe-FegAzII-u5Zn_e8;1pIZ47*czo>>ilU6tbZFbG!N0wYypJ$aHXT6(o<-S_PPf5xJS=i(RQ9}FuVyG5D6#q#%Ul5BzvvxURzHJ+sgA2?otnqVyyVZrjvK%~*L@Qoq~JDP zaz2;T-*6E-leTNr|2$aKpTiMu=NgW%+7J*`hxZ{pfDbk`d9fFHCc-}rHU^_jQJ>%O zeov-<2p3Yunf?l0D);uSWr!a>a}@!{j#0d^8#C89N{G4M<_U_BNPpglR>;G@lAr7H z^RE2-Yx#Lyetu7W{%865pXBE~{QMhv{E_^85Pe%7UX`Cej6Nq1Kf#ZF{9{h}o}~Y8 z^79|%=ikfEU&_yak)Qule*UZc{O|JfSMq}pA@t|Z<>#mJ^MA|F{~|yCkNo_<^7Hd3 z`Dan`n7kw{`WL|>`A;@lrJp+W?_B);nSTCN{rg@0`o%|0v{rmd&->EE%uc_P^ynl4!{~9G3{rE@n+pb?V zeiDslg#Sm33jIwZJNX-VU#FA*RR8{oYC4r0BhOgXa(WETPh*%Cm4aO!KM|xQ%{xyZ zvL*)))rC3`0au&&k_(!U>3W(Mu#;derg=ei*^6lqO4PrB%LWs5QKxu#{xd>PA>`v& zRd{<=L*ynFiGxJJL$Sp5YNi(%Cfi&rY*Y@CShONW2a z5UoRSlT@_$y47%y`1+BT1$l1(cS4Qy?|;_M??w{dG&0PaB!ec~ubYQNGZo7H9$hId zQD3F9K^4h8QB{G5`1`C{=#E_wqIl3<94M&-VXzU$&AYonl9T_VCUR!g8siWg<8%Uc zhDvQ&{Scd-Q}d8+D*4{W0gTOYwn)~6Hu=3zPz5hpgDPx@8jDsO{%nlqLh5V5nIu23 z-?U&PM39qEfb$=ydtzKEn3?T5h&z)PWx5X^ymT32x%+Sz?r8OHdDLk9b&Bw)|E!Fj zbI&!=`?u=X(&&w4$&O&)8Z*Ke>1^Nxv%`U#VsJ6C3*%!3Duxpndke+`bX65@Yf6W@ zG6R{;Xlup=TYi8e-LKzMEx?9%$Hv+^ox^1tc5G~&1g(wLWKeZMMco{ww>D}2t-8yU zN6>h$>xf!4NwrC+caA=0s(ca`F9s5Au_w`9jUuLpFgSCKBpJYe6)<#ihaT3yj#Qee zlCmB0MPt$ajlvO0rSZntRSPtNYGyi$2}c3V6<&*reO8zYt4KO6e zPY3vr8@b7t#v%IYDlocW*t6o&EpLJ^-tLPLAiprBFV9>N`QDwGCA(*? z-Prr!rB|6oDM0TF(F^c7#G%#`rnWVWXAtl#dI6gI*~Zg&R1qDn@-r+zDo27hWeA=n z>RUPC-{Wy_&KzB3#bm>3&_F8fKJ*)x%<2=L@%@Y^Z9W`~R{@xhA2% zZ(~q&<>^#e0vCl-Y(AtGF9vfzu>*IfzK<&$pTLbjyJqaY#f?9{f?hu#y&w;Q;$)qO zn3PtBzK-+hOsI8JFrk!6n!NN2*vBG3=vjXtU2v1!r?Fx4{TnvQ$K$3F`Rt=b=Sp=2 z()8XPSU{bcJ+$k+uFT@TzDrkc;O6wb56--LdiK!1_ts>V^gY~nXy)59-?{Y0jZ1Ix zaN`cVsHbt126wsq;6f&(G@y4#;h(dZ!|96DZ`JGfUs)+_d+4MEs2?}U_Vr1(ju znoFAK+IvIkt~7O;?zrb0$?(8g5QL9^Ihzc~vERw9C$}IkICQ}*thlLN>Y21k^~p6aXF!ZUrV^-WVss?V zrgPFpIZHDmfO=ZBKnLm>raskC3U;Z@+J+fV#SRM8YTE%-P-JfRk*G1xUdF z=MCciOE{GCqilL;lK)`xCV!Y7ndCow!V4v2n2)N%-&Q>Ax57iRrFu1)7w?kuXiW^gVo((Q(#uC-sc##%oN7ZEk(A!!@MDd2?b%x2^bM*{;hVjDTF-zo5bx@%Yg)iQbaf5sLI@&{ z>tGN_cCrCCGcxnMsg`zEUL@-hMP{yCdH2T6#dy%y+0c!{Mxo-tITDHa;CE~D%@Oqt zT(m&rMwAc##(20mb%y5~ij9s53AcfO){7$&-m;9L!%bkKj%?i;54leid{m#};0qm4 zoGx+T5bLQ3wI?}|KDwyWYt@~)UkkWjbgAc3ibm|+>RG#Iz*X%POM>3vqKRKv?@)LB z-qAp53Cm!J|Wr@Ooeak}IRVQ6$czx&fuF93` zyY(e@SWl^jUZ_3Zy>-f6PnVXkN7O^?BPFgc)WoV@y8VrY+53Q$Rdfp;ePZYH^vBii zUOoh4@2VYn+4QDHEO(hfCU3vM#K)<3G5^S@-xe_n2(8%xx=GU&LB)b|+bncYkn4=1|WE zi!zU-^}ahW>-|cJ-fg1#%zD(&DjH$Z0BshglX?eEtRk6KD9Zx zMY^E9?^r;(>=PDU`pG$zu3nj@1_Cul#&0M0`fZLt@Nq&>c#ywATKMRu!=0Of9 zUUe;zwDp|YCzI+f_8&(md-{!G-j8*xI@XxPenL`Lr?LjFl};&Br5b-nwf^MibiKKS zVM}fPdroZ#Lj-V>TL$jixJ|S%7Wp9eaU8XR;}6d9=0`1`8KiZo4H&)2*`4p-k?DsD zerpL?|F1!uVf~CNzTdwW`vR|gur?F-4_tcn#>{IsW?pB=+KDJPQ#^Y6=PzKjyr`4N^B;@`?o-mMW4>zLmO|_wmEZ_)c#0O29t&>1Tapx2^M+}ST;{}jKp(VOSyaw= zP+q+yX0J{$CyEAR9;|RF=_Uiy4x%0n>bm6ybw;oG$}8De_P-6Nv0m~LB%GO`J01^8 zGIH^N1a!mIG1=%$xV5Beh}MEEyPvr9QAQYsEKQs1k;HwebF_x=7@m`L=&`EB_o{9< zGowYEmE`NS{uMcegvNS~6vlC+9|ysKMKXI#;MPHXpa|E}QB*L@*O&9*HvF2IzzN|$z0A7Vr` z{bc-hg8w-+mx@zeTl)LH=x!%8C`&$24+x7vI1XqQ8*nW4VCB_wvwa_I&J0vu`{Bk+ zf4pJf((5zdzA^Ki8#8Y(tIhowR&!iv`7I$yx7%krzQWbQ#7y^)@9Sx(7z}6VSXPLJ z8`vs^SaP11gL6*kp&@Btc`K2Y8Yf=Bh6o1RkVKndNMxJ9wBYC z-Cd9IzMF#7Og9XwOf=lRRjxg2%#+W?w6|)|eBc76BIXcQoz};z2r8Y+TK-O&YUpIb zqip+gvmTk_swO(nVo}Wl7voNfA-d*mr_akmslN{tjKb;egi`CvMROZ*THSk2UGUw0 z0C>;deP4cb&tSY4yKoO3y!Y`h$LI5L`Al(oY?OEC;LQ?Ck$8Q1jD(J?kBnsSZi6_v ziXgx(zrKkPiRB{Q{7W_pF%9aO?$5Q_MVB5rZfmXDdQXy8HFw8**~~HArg#J9FXnXG z0rGc3U)Rczw|A6~rCI-JLt-; zPD(M>lpQTCQw!`ia9nvQy~;Ka-(7FD{Ue2gM&|*PabePmgM9^PGHdcOngoPhN!vVR zHPhEi@<-rT!)&rt)5+gxI`K#LqQh_-J86X@d+}oyR6kUAkU{VXI!4Rmd(yr73o}Ma z8w^@xqQH%=P&P03&3NOCfyIB%iLglz?hjS|;|A9lU@^AV3Zci15Ljbu8Eu>;h7Aej z+z7v;+5u?grB07WJhQ>w2F|ol_@P?G6$&Q)Kl0uLJg)0H6D;yQnksGrz)hmKyaFi! z6iKKG04a$kDej91E+8n&CMa43ph!Z(Ml2#}%ZX7y96RwMk)lXR)aImZb=q+&{NmVg zyYmsGlsL|0GLn<&OyV!&>DWm+?N4b#DG=>AdGnuhmv`S*1yHiv^9^jV-n;Lv=bn4+ zx#ylm%$i`gFg<9+2b=Jv?OCPNLD2G~`|gZr68!5Hw4qoZTx+$0_N?c~%%TSZ!6QJ| z?BI&c@hilNh|Yq%(7>S2BT?Bq0wLkLN`)<%Nj4gd>&9&Sbt4Bm1zWEEjA1_Rr4D#!e{Fb|GeNGsQ(kXilbXL_=P~e+C6}%c9}=18YBR=mJ#)02xP2 z1#@S)z!31DO%&A zfO#H|3}iSRQQI(e@P8Jqu8Mc$z-}w+tiNCJ{tR1_`t4{;4<3%B{i0f~H=CrWOQ$V;c zV(@Mm^iV3_{%O$)f60ewx^uxB#B7(wJZ`d*{38-O2YYb7ggg!NaHl2b^k<=G>yR%e z=R^C5;cy(xd#yKYNH29&QJt=~))G%SQkK|?GXyE>jqcc)zsMSUwTblLr5kG!d(u}P zyh0b`V>O7GI98T8NMoEb8=Pj{N4f_Pe)@?VZUJD8g*flT13kFsFxVoPHPko{L2+qY ze{aXC?KqD@z~A0q!<^?M*3?y-@YDt-Og*{H)=W71XQ4SF#^KA9N07yxX>W?&G=5xYFY7JD1?5p|) zHzA!p{bzZWXr}1TxL~fXG6qk=Lsbs)#7C-D;{3m-UtNsT2_dxm|I}MyuKNSN?NA~- z6*1mkNui5B&nGy&98H3c%fXF&E$m=_fO^74sH0WG^~Oq?H8Bf8Im;(4_k+?ohuVat zlMcjRBWE2BYjg9zJa)mWIc>R){wyYp(}YWJ4B7NfDQ^fX9pRsa7C_cvZ6csqmedun z3fj6E=N{{BLG*b7DQ)z`Ac(C|b4>(Xahemima=d1fOiHsWO5MV2u9V3ZkqLJWFK4I8T@=-LPI`KG_+WjrDhKG_`H`nSwL2ERh~r%zCq< zshz_Yky4*%OpRn{jgAgIH`@5%@C!(M5bu{sjb-pk#uXlW2z^NS~=wE%vi*nbf6hM7V%{WzDuFAO|BI5u^ep=$Rfl3z| zejY_%IU!h+dkLJ(gaCV&;mXR#{Fw_r)Km9G>jOuQygjAJevZzj0?w}l3^L4og8>f% zz1N|BML$mI>3#@54x+x@8%N7k) z!~$$7RZVuK+*^9#lbdKP3>Vj31R}seKjVqOa$MyLn^UnsxoXB#+I++zF@XH&qXU%) zFjwaK4IYI7;`>qeVP%WAUfv>N+eDH&1=FK$hxZfu8Ue0-CDby}+oNubJQ9=przlvBTX%>XHDV;h4O^)qASg?f z<5?V(oaG_fAK8-Y{)<>HL8Vf#bFRAyVuxB-D+vDWNvD&jiV)oMBQQ@98dFCTrHJ9> zENaPtlZs>qt9+OPWj=3hn$`GdcQf!3oNr8pCXz|bLSE{!x;VCvdu!~y#QNzLPzd}T z_ovoX#M z3ZoW4+<1IEMQn{lmPv`;27FP*worUg1skbTt0hc=|HX2%o1w1MQ`iRO+}=WCdhprn zOA@tpY0^Ij?>+va<)o5|eXbPz{`Q$xz zdN`RuzIQoa?e%{W`H;09`POp2b@}AI_I<<2`wRAZ)cZlIcli{mcf<8%Q@GxZ`Q#?# ze}MCE&L_9nTZfa|kZ&*N+kSnQ$hRY(+=+aI1HCu`*)xgGuvNzu{^o9toH_@)4arCcZ7Yp7)^DgGPXF>b* z%A_Mcfx&_IPL@@{YfL*LQ^}1@e8bMhuo9}nP;FMa&MI?k2C=`0S}2B(ls2p<(Sj;W zcRErJnv~_y7tX1|`{3p`VjnD4v8Y5;YwlZ;p5Mbd`LdanYA@pf`p`Xs7Ei3zYTwBC z=34Ka6X}Dod=y0$L-K>U*3dmt&OyLvh^%56t>-frK|-W5VDEvYSlsR^q2ODK6YYbN zcl`{zppX=RFc+f0OR0a)?2Q2rF=u*C2m~ZIB%X}XP}Rv=NIDy1Pu$e0`4x3ZX2|T& zYHg5eBGFBB$}r4PjlS2OX_}?xRRN$RP`wHM#ewPM`}FewDdQl>Y<)Zf^sm+*2$AOX z1&~=@pGz#xxl!ETHEth+dom|L*tL6q5xq5}zJNiMj4Dp8vO`>`;eybT#{A8oW34JYp?*oz^`qj5lb|h2T#-}c%mQUwxb#wvrtUL25XhGvLNnV zy85Gc@d;-APH{$scH$~Jaq~V&4bME>n~Px_c-x*(Zfu_rjV7>$z-)n{3Ft@tf-R z&8f!D1~0_~0CRlQ#FbEZwW-4_C^xLE0Sd8L0#?_keS1Y_o zyTSGIem1Kh;NP|k_Cb&lm5nziW4tO|Y~*YwrO7kr4RZ(#Yc$7m=vN~N5nok(uvxC7 zysZAlm1+LlUL+zIVpCn;C%!MB*{Y{1{Y7Vpp(SlGlPl z;XdY2)0NTG*5UJbS&}%EZpuG{_Z`E};(bj5uNM#DefRKlc;8C5GY@cyq31^%_YT9n zc;pBrKFlRwyaJ=W(Z&PA&{=mzK*3TYr>Kf#IqDz^*J#fjPR_%(U!kiP9Q5o3!^wqs zzd-M|;r;gE|Ggb|svF;%%X#7E3ZIwwkrVbDZI^i*rt#Yee z`8LAUrtm=RBRQ&GWE5YKO_lIx{Jga&*d!BML z8mzEpHjz+N$ciHSgIxO%wwa`^#j=%KGq&`eD3-(hM{ACPMUnP?YRzI%bYZ$Y z7847x#wH@^zTQcoxRCA8*b5>+;&hXyPzBZ8CNBAesBOIii)W9V_^S+IT11^u z^{sMh!J40KRBu zAp|^MlcDJjNLAyf*<0liz!J517c24A7-S%>9s}=4t4aDgu{D`w2ld3kTHT}-8=NLhasBaMnw6@GuFIH$MRm)(&#V$MnmCwyuo6z^ zRdtn68XD+nZ*w>v*H-d=4gKWSUU-X!$f^Cf())*5aDR6<_AY3*tgn_jENWHjJD4zI z@-DR8Q>+?)lTP&h&uyqM0z#AW&5bA<OjR2a8nV2M3A%rzE`~`vf-e5JUu7&CjwWBhE6e=gE~G-jJTfJ?Pw# zuqU)k^x{Y3)#xd1AiU)*-XDhQGfaBO%`;0?L+f&lHQh-a844+3tcq>vUQzRoQzV`h z8dFT;e93+%FCgHdhF4inff}4DWXcB5!I1k@q4L%7rHRb;E0tGvTsbwi4Em`VGK$I! zUdlh$TY%wP?Lk;Z;hz`AcPGeDxVAC>Qg30b9!jjv;LA|ONWs(7_(z4Or?Jl`?77g9 zE75GY--7hb_pt6<*_-8dMnC|MFtSB0{{PmL+ zH1%$#xGn5AZ8kE@wx$TbPoD)wuC}Hg*o%^zEPLmcJq`Sn+;OG{*aS^lygpz}UCr4M zm$bWI6eO)#xRF9#-;2}8B-2;-TSY)inx&XrL5bj$VxXhnp`fAIpv2GNY>Qy8Wh&$h zQUz8^8oV#^iFjnT&|`EAisoN2m zU+7ngxv@rsYhgne2^%US_M652GZ!FJ!kAY_YjA`k+i}-f4kAp}#lV3Lz{OA?Eo>^88^W zEBgVmC+GvOnXEGUp=UU>jN!3`)}zD_#8gAoz;fI|CGjXiK8Sm!^`z0AOjdIitg)5e zDkm%xRnR(4CbNz(oKm&wYN_n1%#0-Es#+<_5Id1XRP-m>y9fHTFE%D5s7tM*W;Ec> z;(;WDS63|iHd7HV$Q=WucZv)1E^N_kc)?hGESTsC4eu_c-uhWZXwAkMs{(hLJ-_Abo)Md@*i*f2g8kXbtx2THJ&C=Vlla=p z>uo6!&K%(K+M18f-JtW)(lc&c{hL!_9DN$DyHT3#l}m`Ou~a z`%YEmzn?nGlvAuw={Uv7?*I1GTcdNhonjV3I@RB^+{M_`;S~WWpu7Y#sf2$(;g}eE zCfiY}*cY^s2_x-bLC{fEg?2QYkuHg~SV>hw+ZIJgRPVHL8l^v!?SLO+aEW4-$)@&n zHL#hP$|!p(Fgv30wZb2AZH-eYW&Dr{^pH9*t9H{SpsM1|z~`{pr=WD{x2@aCua+g! zsY-BNsVxr;og2-chf%k7rKu+3%b|(Ls!zLOXW}cx2kH5V))8wkCJITB+YT2v$t09n z`uaQjF<<->oE@

S+c1lX~#pbV-P<1;7wqlA%?}e2Ib+F%A!f73$#uytydT@I9-{ z@BvOU7KAR4_#>q~Bf8(S_F?jXPdKUdt#`J+l;r)nJ=h} zz_Eu*IEZuKwU&7!0)mBbi7JioLyk~`rVQ_0Y4S(DNtS4jBulJYQ{%}*^XS+Nupz~~ z`~&Z%SsBO`Jkb!HFFcn17%ug3Km$PNjA%pw@cBsjX4o@bQsIqzL_ZzXPmk)SHhRkG zSNA~B8(k7^Eym|0HiHi!#_HCD7>}|2inZk0V6&FQFaoX(VIN!KZS8EUCl`-!NztM$I~-I%vu1avuc@F_Ou^uwEf%7=&IQOF^(rKv1_!5UiXm z1VwWiS{`rijoh4IuXkP%4H#NklZ9Xo;%1-#6KO&`T14?vK9?M>i3Q9J(Q9xz+yhu> zH_U}5_S#7=wDsgA#h4&@I-%4FEVGD<3fHt5c5jcpvbgF|5p}b{{!k>sc`SIN&iEny zbXY%qSU(-pPc8Zh-r%)66jibHDb`ZD28lO>+Kq6IyD-t%Lk<@U=|lPR<98+w^^Db# zhS4+QnM4ykAMP1FRk*e}kvWK9OzF|XJBKb7t{q5Z8uORvbq~x^t{qEc4ndBU9zE1B z^kU&!XCl*-e+jQm2Zo@ybnTZCnZr<7qU?4B>15J|JrBG5bi-H7r;X=U!ws_s4EGb7 zHCSxf2aEDo?tQ#j&@sO?+f~&s)Q{l`<0NiUPm>u(FB=d#AItT26XSaPO zMB3C-I0zV&Co*s!jQ_@WC9XM8mm>RIIH?+ZW*qi7hY%`a%)yy|2d)-ivs1`4;^sjl zmm?XzNFnm0k(2jIlHQ5GuhEmYt%z48|KBL0L5TGd)Ov9-NcR*3V-(rW=>n&Vp|-(0 z!T|J5g%E*O5*#X1L~>7t%DKSo)fq`#25C}doXRzeiZ(c1F(PovM1Rd(VSC>}>IsSM zOXL@&;8l})i#GWeY8kxl)kpf3G}}z_#O|V4U}!3<>^kb^fvs{f-dJ^>xaN&!m3w$k zX3gA=5s<;~wYEdEHF9cqSW6bXGCm`*`S{pOtgr>J?%H-`@R=R?XO9;a2#N@{b!s{G z;HjPa|8N8fw^Q#=`!?iPrlKx=nb{ggIPbOoP3e9T%99erKo3YYE{M7jP=mc%InLp& z+v&gq7Z>acrmvd>i*yGGQG2epE8FqG8A19-yQRH<3-Lg;>+Y+sTU)<&Etp@T*}}E#RPDY*g5P!jUb(LN zl0T3QR*qbMFG^?P^Z4qRoJDBKhKw-hx;xZbZB-9?xt>rdKT!`ZE#NQWzgKmN`!IZy zyK}sE*iY#{vTRa+=Z)v5jpqyY=b2oJL($7t88+1M*$^{=nVMrTQ=-u?#vw_AwKm&= zK->fUaGNhgyj{5yFf)Dv_8*F0p^Z=Myz~ikV+zFoz)EgLtKe>#kda(TJL{^7XK5R( zyRv<>lxVutl0sw^ZYSHPbiE8Yvw+BQYq_)ebx8iUz)Re=EBR;O*m-O|2E5Z5ytw;H z9t2f%1Qc~DNxF>QOvO4P6TccW?OvG{520Vq_HA!Jf%robKpPOoLIa1T3df9?5y24v zckAz4xe|`x434}k+%sbYv5oLUV9=8bsjcyw69LSclGFm%~OXfj*bYc4sQ zWZW@&U!_nzmb^7sYt-7up^vaA1bO3osOenkrej&s25S|nK0u6Oe9Q84sNoZ(8$J;~ zT^TgYfwGIX?d1qH{}Y6QX#5>uPa`cGr;%{SF&FP=734v_7Q}a?@p!fsl)F9KL6jRx z%NsVr=cGw7C|nw@*Yt&injmZqD{;h+}qyjbVF!+9D(|9!5yang}GU&vPl%oA&4>- zWmT{iPu=MOI1+C}kS9%V#1^48TUUlQih{}A%vK5-t_HgJlOH|su4O++=yA0ir71Nywxxf$0cgHMm&o@nkXq;R_H$Lsl1h@^WI?|3;? zc(V$^w&wB25}8WcPSZHF;OSZDN*&B!9M9oUbE)x4{hJ5LonH^R9H<*Vme`Buv5#U& zrbow)<7cc&UTw~TjzR+SMvMp165@O0-=C{T!A6~ISTvLMiymB`T(x73fi^t3=Vd2Sqxc8$brmB zt^w~11raT7;%TGc_1EA0<`4tE;;(HqaUxCvg$X zicrin3`|$nEog%Qvd=~#jN#PCqEx1}z$c9!)NDh|Fr zzA{0<2g|WbefQ7V_7$q`tx}`YQkx z{)5_bkJzH4pyHO%$``9A2|3XKl#y9TlvIxxG5U@ES5uD>kSgtqQ$-6MK1yY|O&cAC zfl0_H2eODZl%9<&fNJrlN~0=QW+Zt)0K`cTHQZU&ovE_dZ_G}lfE75{H;*qMUU(sPT!gNE zx*~rLiV+?ig8H9a9K2M(+{UqJ&sx9h+4zI_(3048G6%Cla$gOjNE`xBJ@7cUsv(hSH5TJTOOwK+g|Ci*PbV5t00FhfP%^`U$@%7b*zNaB~MfER!G z0c#ERhQX(|UMU--;9%v07qC<-8ll&DXCl4pO2yzMa7lL&m$XNcHH3JAVkXqHoef}n z@LNM}jUTXg!p&jJS)sZ;1)AFN>-Ikyj3@{iIiX21iz5yuhH9i>R*6@rayDRt#uB0- znC0=QriS)zMn2<8+zhY@&|bj%K^#&wSbr5Bh{0Srz_WJ3vv$S*lIR>S8|6IE znN=}`kOegx-$2L>Ew?^{X9SvqGc-(> zBq|xE(6}x@*Fg9t=j+JWU9-U&@&M&+pBg@(c-=kVSjmY~wikpH(sUeW$-JDErVaRE zU-SSt@nOjz^y;ERCjy%_BEykE; zh%rGXhA~~Cz@SIyq|Aaz!BuH^kjA|+q+;V?qY3CyXVN!29$|JdiSc}vr>N}ar|7dq zXNbaAL)^L{v+ia`#5GK6h6wUsBFiyTenpAr;&zF3)`ELPM+H{!@t8WUsKHA->9fsA z=V-9+$E0siAga4r5OEEY0nuun^f@;_>8p!QIvXF+q}SivThSGf_db1|H?_&;rQQOQ#tK*kJVq{R&5lSS`CIYO_$R9YXKgSb#8hsmW98jV z9&ZT#%3G@-Jt{{LD&#wA|B-)44Lqco28E~a#$vc8X)i&)aT$i=H`FHu{a!pNJbA|> zcGygc(C>Gwc16Eyh2aP-gh={LT-%9UZ#?;y874}-?=Tl~DeCPTP%QPvXcYCXy|*~p z$Bsr3?}?5^(C#HSKRrcgH^!rAckTT*IUYs2CpsQMWtW#W(_3YbqL3<7xJDaM6?zGbM2 zZ#N?%g_|v^>q!>2uUoseZXMs*OD@tk-g8zB7{=S95aD17T5?qh2l_mtW7W(;hOATs z$b5KCNw^NN%G%nWXzwHAJDjaa4Gpq^wefq=JJy#pW0OSqrkg|@RR?x119z&^khp+; z8;5_X2BPlSf}O1@O|2!mxPQ(dT=YJJkZXs~>n7mH(=IsM+7g_tgi(0!Qg!?s;po0X z3$NO@@QUncX`lWSmR!9co_fhe9FpWwGhUTvowZaW4#jiUnVtc`|(2$3KEF0TlJk;!1~4Eepl=^XcGMalle zYL%?BK4@x{Y^d|2q0t?wT2ibxC-^*2#p?@Zh|rZ{qfP(|7cD|wJS}{|V#wki4goS& zD`lPaL_jNLLtP|;u@{aUgNOISQaj&A@TCM%2sC|lM~-=2$sG(EhoJa^f<;jCneUAh z`2+%l5H~^1*IBPAN(^kwlwbqYz8NM%suBZMM3{dL^nBL)48)tI=YXW5#RSRP;`?Ge zBvmCpPe_8ImnM(%19V(dNJ*?I*v@MHpW9Sn9*E$z{ZeCA!N}eVZmqC)J zr(JypJc;i*g7ZKq8pu=Yc}fY$rnvyz{b{ACfV7#LsPh-6Eb32*xtrmRAAN>?NUll95mG&0(rr_YmZ~z+v0X&jOsTT9 z@nvBY>~FU=&~9CP*}7oaxN>0zEPh zz8AJ^9XekaS&B>38+RqP_dreQNP%4LB3LPmRz~in5)UMH_l#{x?7{88E_``#@6hGK zNFx>bNMc{lSaV`OzGU&`fL%5On-wzHsDOI$$FTzVAO8}YdXduUAOA`d`S~N2w5FJ= zZfX)scWNb-q$g{NxdO$43t!@6!9CS}T$r>yc}6T8a%4<4kLG=hSR}Q;Rk2J6cTIeP za(!TS2%{uAN`*!Pn+4`)*6L*!st=#&8rj28!ZL;4MY`u-O5XY@-C}42gqtNsuoe*q zgKJVPZ`@&t^<5taG%D>MB%yQ=Ek9%}Vzk5+KG5>k#1^2^e4x@++}05u;c7wPi$*3z zQ`f}L`X}V1Z_T!C>u!S}t!t0C1p(m=VS&0HF<=-3vtYv1kO>>&5hXmt!#u>Ja^*!2 zQ6n~E_e~Cwx@nBiLrj1F>o8^h5r%M=XFhr@;8S>rAGW^G44Y)kAlW3-y{sTCM{g`8 zqC(!|MsVXVh)VN#9TAkZOi(_ER3<1#$M+C1*~`SF5vh`xG~Z}R*cClvIXI95Zaj`J zHY`tuk_!q)Z+s$QFYFonWWv4;pFTyOZnqZ=!J8bR&f@o*1o(svOiS_fO7*%ZQU{wV zj?e)B5uM^*%Rv>T5btR!Xrtzq)S&!Laj!l2g|;kO$+4?6FsOAA#P=9{w?M~m>)?5W zPutlHYt3D!Mu!kMt#Iv*L`JO3eS`cMu;>7 z1u58z;Kz|eg4j#*$z`_Fe62NMr%sJtdn{pBo*G3YkfG#?!nG%;%$@dKLmU)sFu9Ul z;@hk9$u)M((Q6kI_B|-`Qo>GCnaohKws7sYs7#$*kML&&d)*+#J{wHlZ?8w>GkZhx zwLd{bZIpRCVQ-=`4-6$Y7q0ycl|j(6Ap||Mw+|+F*gFT4yKE8R?C3QY?D<|4`oI*s zfeP&#O71UQtC)g92ke7G$wnq3hgb@*jD1ilRtmqiB+-ATNVWSY0&WSG$6kiS)e(#j zk|$8n5w3{HiL5BhDNU@RHr*rML2bwAmdRR8rZz`0V6wI_FE+8-j_N@&oP2#VDd%Kr z(?r(Sw=k&%`xtRBbKPHSM~KO6hx8P&k3DWedkjIZ9LY6k?|4u&?tukHcW>um97bTg zqTEXew{v~0hP+&o2Jfsy?9dkF)(@pPP!_M#^@1y<3}i^}F{Hfup(9J@r4Sn#B9fZ4 zPr9dq@Jy_$f>`CZ{Mmg=Ru;r9Y2BM9c)k-9HM= zXis=1qBiLk>)%aok8YmK9(`2q(L>3~Aw=rB)=it{3j59>#ILcd29wqHN`xx4DMqQi z=IFJj5_S#k{`U+e(}io7DRaiI9ZJ^mW?8Q`OP>}>7l^p=H%)7yEY{W}@J&(N_m}HF zVbwq?MrznFl+3M552ZAvC%No-3PA5&D=7{?j{(jipkLhg0&fQDG z@LBcKb8GY{rD;t-vfy|i?8GR!SvW9aD42S@TwhV&MecyL8rF--(twe^h`S8uwjrCiEYAY2ZZCEn-9VY3a(}Y-d3C~xkvdnXg!wc4|Ky(oX2&M3Dz*<1+ z{Jvf({9>J<2LjG|YT_=`(4!@W8aT}Z-|FazA&jNDn>J12;d|yT-GNX|A@)+p+WaDK z;!}NV_%6LN7;rIbxYU%qXw}B9VL?*WB6b|8uIowjt>ZuTcjf(D#wBs;vNhjat7ab= z7yOHI8V{hzs=RE?)00oDlJiF`%ZVt+4)62=|pu?tc{yKGfe(~pauK!Y$^BY zJ;3ugt#?1?$#)Nv)CN@~%K58~Sk-Oumx8WbPs}#9b_|2Y^u=0_z06wBu69F^D za?nZ$%GOQcIyrrEU~tA$Lq~ciT!T9K@MrOlrs>TN3_nnVzPj<>Q`Oe9XkKlXhkaZ4 z3h7NWVb6y;V5Q2$RQC<(;!}NNZ?+~M+n~UM>@(E!d7rmZ*R)5DcWQf0l}1g%sjcO) z;9u4sx9O*UARmQ7J6r4!U$*Xor2|}Fm&1$l(}jwwm5J2W!??yIugc`#_MxF?Ma<^|a1(%6ML zF987>0;V~yn23KsMJ=>oV;!haMFbsTc1nSgQ4ya%!j72N>lyw2G&r`he2YRmKBt!B zolpMnx(;L$VYLjby{NzbmVR>GIYGR=Xx(vjdID$p(XHEnr&s4E;P!Cv;^=Pj2T2!J zkZXk8V*)A@(EwYjo=a;yj0|Br=%QZ-GnoxkTXbpC)j!?jKmsvMKETY-f(i zQM0FhVw_SyZ9Kh-D%F>&fCR}?u&5H9sxm{6h@?PGd=?GXL%U`0hWNLvhT@-G%fRwo+w=U*PR%gAl!$VOZm+H>>Cd879HJnjld z_T#G1zAca7<@O>3^(~AbGSy;xNgnZhZHiaUGmydzRNMFY0G+Wdk!Bo`GDxu~pCL!j z*mqHi!GdM1X(w|X9i*hppA5ma@o5E>GNjbTr%L*CuXk4{O0gTX#nXAVaF<(G8PmF0QbU<42dfHv*+W z9IQE4abM2vP`AM;J1u`wI5i2t39RZFUcWpvkSt2%qTbS%>uPHn!0m+Wt>~Kww-CFo zy;g)hx{MXL!bdB$T@_J~cv|^?m^4h#$`5!u{PXJT_tk@2tDpW-Ke@MfkE`K`(#HTi zZ>_%ify9wlMnMw_h^BDl)zPD*#&q?LMB^)?N4F1MC|tcO(fI0UGwCo9XyMe?O5&^v zWPvwM50V*c2BqRa>Puc_O(&cTq}`GUXN;BgxJm2BwB=@dbF|1%m7EW1Pxu6lK{TB~ zBOIlOF6ttr<>qnI+q9;emsOXRBPm%_<}=Dgw8{Lg|MoyZC5-3ax0wQNr=7 z^AbCD=CR-9pDSF2*I$YsxHo^faCLQJpGUc`1}XQ|!=T&<7Ra#zDIB}KIFD%TG_6a) z#!?(W5e#sRHKUKrQOI3JpE1XfGCw}`W5}i-U;#0$6}~miTsDfcW8*c_u0-yW8S>V} z9=ldsvk@nv|18`XkQ~)4%)W<(17a9gbai*F$#q6%!~%y}3TdBOvqWXMUd)~~>nes) z?oRjzfin_8h!Bi#<3VH8tD92s_)F9!4%k+ZKhtSv+usAq*~tIB%Tj&EYtpHibHPrm2OqXvy38 z*?iQfL{Rw!gGi5JL@bi#%)d z659%gnqKGA{?%oPox5IVQbU9SUS-HBv};YL&f-6VJjhAH6!PQ*zFJc=;QR1z9sZ4& zoMJ$KrX$zaC)etl4EcwO8KCTxf(C(l18Gi2Pmk`V&R>Fnq{)1Z(N($$aZ3kfl!wSGa{@r_sPLq>*g5(^*qICOM0uH(t5tLy-Pw69whOCkWL=n(k_Viz<#8`0+>gYK(fDtW?;!vI+rj= z7`1^{e+lAULx5au&A2)Zj#eWA1;=x27~^tm+B;6|_i+en>Zx^nrp7#h%`zWbrlU0z zg2%IEZwox9OzH?bmDw^K&e^?y8&Sn*t+cAH&Q1U^j*OBACwXK|C9W=_lnYS@S2|0t zvywV4Bo<7-v_dp~1^J@#8t1DbIMHCeq+5`f(F_PZqN2Wc;XJEX4o{xW9-Kw*yd!D| zM4!Bg8l%py7pz5CQW}zQG5XIG|A6-vT2Y2KUV=-Z0!ID!37bRzi8Rvtg&g({R1nw(zoZ~~rOc5dDpGzU*Z0|I%OCcrv z_=~?EsR!4)a`fWVRqL;w;O4*TG_93K81AKZdXGPldW8Ct6%(u(}^vmF0s^ohod}eE3TKTu-4f15M6!ZFz0}{LnL_I2fg~Ovc%J zWQ%o1EVslocoYVx-6vx>24JswRBNJ(RSgBv)XHbQ&&R&HR+|H z5mhP(8TT&Bsc5{?Qm6oc;5?FIW1eHr_~jKT93I6iki7w%?g9v}C_wzk{#^VfpS6As z)&&F>c%a4bLQ@WBm*`=&n16Wb^7_irvmu>T_o~gp3NHX`|M+;!j8i=W018!x+GB9R zU12SWZ>dN75Z-KorD zbwD+Cm?t~~k|&sI@~nsPXV&?AWQsBCLTPdv1L-haKGx_@0=iV*@9{E_fg40rjCH^% zkvXu`+-hsZT)0Sr`?y!f=Oz%!2eBaL!88Gh=UNMM2hTV9Y=6mB3@02Xh4T$A#mE(-P|u;DDsU5FEPw4M!oAD; za?V2o1s33rQA!bw4+AU!hFTS@=z=6Mm%NIF>&n`->u{ST{8V0{N zwWK*Hk?U;~jRto zb*wv}%=Ktt3rw@Jg@Y}H?FcW>S~%ENXv}$giftdBI*%}72?%Lyz&@IiUzG#H9ziS{ zk#3xKn)q}BjL>7HFI)XPM=ixi*Ly3ysG(D4V~e3rV2K1M=o=*f?gAiqkFaHOJFWTU zsR}FW3IYDS!K>=X|%v*%qdGuNeZHrK>oIVd!v5f~u!>lM%t4?Qdimv^XBKD5(q<$fnIbKTP^#02Kq>6zG#E1s6pSn zdY!>+duxD3&x|Y0B-$poV1K1a+D<(EHU9ggPPyT~;jWY1E#3X$?ufHp)Yp8A;o)NN zUlQ0}5GY(aKc|o_UNp8oG191wX^gs{IjSC};8F|raLVKV@Cv*F;HYe39>S1?J4hMBvI@DF# zA}(61NG=Rmk)SQTW9Kfo0^T$Hd?CHJ0TxGB4h%!UmLB8)c#N2^C-%BdHMs6&Yer|^ z6Ro}dHEkUvQxo&SpRi4B_p+a}rthYExI0PA`iR{MPl*qA_ja_UymM8znY$cbnH?E} zs~w1UXPYSJ3pA!nTqriF|BZ~*qu{r5=N@8JAzSdqz=8{cU$zZbmR!FvW zBKaMH{YaG@7I_(NVxctENn0I!Gf|EzIZ$15WG$iL#@5GC#&nN5x+$E3hluJ($W!o_ z^0epBX>U7D8Jz06r_@8AdeD$Otx}ZMs(V(Yd_q0^vU(U)44b>Lfa)^Sc2HhX34JL|&T(;U6am-bF9O2-YnD7Hp6A_#TA3ych9c>%_bjOy~HOoFxB zTD19)H=y*P$l9nuih6usWati2 z>rD**wB>AW3JyQrv;sOg1cVC$)rdh*K$sX9{&+%Q@IZ31Hz&4Mj3jpEIp8Q~pamo& zn@j{Kx{Qosg7?GJ;!OZ-0I`FaDj|^aDFh+OeD@zUX=2JM1teizW7?#j41onJPT($G zjy&lQDGXa8qz8pDjU9K3NR$|ojHR%ExL$;vMWyCpD8z^LS+n=oaG7$2cnh`9XnSEvBCjeVHf_25+9k6a~hgO%xa&V15y(%tXQV@erZ;?~@=jkqhBxL~GWT zrZvA>g4TSdB&`8U@8g*?tua4Lh}Qi2#A%J}RuNhgY+X_F_m9>DTfihn(wfgrp4Jo{ z9@Cm&+hLOE^=#m*;>T)Q~{Q&sVdR+k% zu$}7Mi@Nm}lD}a}y>G#$YSe+fEdBQX5C;d6YRaQ%7xCofq7DM1dT;5K1}A2FI}0kZy=O`4Ww842cUxW>J7?3i13`d)gB`?m z$K1^bAB9st+#`{Ua`1w_I`^qOJQ*Pr7OQXR`y(25@37X<04FXRrcwEY!;nnVy(b2w z>;s#8b&LXH-C$z60 z#wAMkXwhjDKaa8NN=hiJE|q z0^O&UqD?I@t-xiI$sqwct_M@yR6g-b{AQ*xjR7i z$Zol(*!_g=nH}$$!9``Os1grl1K=gRr|U`KLPJrDiCc|R3njfjn47HYe&1SAhFeI4 zeLY1Q3yTtEjnG**H-zZacR*_)lNx+RA`L7@csbO_tN4F~=7Q^ zL82^OHV6=lkZngaH41>2*Z6sX=8+_Gk@c|4{6Md+yZkl4v}_b`p~R=b|zz47GevbBuv zD$#<+v@o5cRAyY&O4qgRqGLTmO*&AD)dvRs)w}~L)@;UiMT+*hRUb%c+`>T&cQMqF zz1A(zWP9uHL@#Kni|iY?fYC!jwcy3mPBlZ7Zy5zyTN!32-5**>+MVA1Zxd|h0Yo;B zyj!BtpxHVF>1v4%=6)5`0qE_?JF&8K<#&I~Ag93=Ww8M_AB(L#g=f3Kc%vs1LVq$?%P~tHw!>2b8+cp3U0`^S04$p{$LT`c@h>I90 zT71izk*a*VZ4yoHZ0kP2ssoIfovc+!|Edn81tFlMN%X^=Zmpmt3;uVIgK}<7T~R?f z(+J85&c3iyptqrm?Yahd8?ExzY!`MbR&7AkXWTMCCt$6HdQv@Iu*&%>)XB*FuOru= zgZ4m?o#HX;_o=`h+`{sxaUVjp3*%jW1l`j#2@Wcu8bBGvJa7hD*||0!_bN)LKt!Rw z(*-}b#+wEXywoK9vj>VaG{2m{2q4~%IaQow4lB&J9k)`Y04=qs6u4a1V312f*>&Gk z1&e510C&IJ-kCk2wB~#er2w=Dxt-+^u69l%2rc}_LEnQ*tfoiI9En1pgry;2PQk-e z7wrqBSkX@Sqg- zf1I%2BdFZ5xH9sySTs?vf8agB9>Bal8Nh~tkCZQ}8YTyS z9&j}HPrv`r*Nd|lgd@MfDvD`f0R9)P?VE)j=-_$8VcR}qkPJnKpbw_CT6{$)-78N-Awz;a9{ zEY7hm=q78XV)0bM^yA&#F!1X2G(}PMRlC)87h2O`MUW#M(oJgWTtwL^TA$b_BIPlU zSuXerRu-l633FS1CXkSwxfeO-RcdUk-6?rCF&bWp#JzYD&MG<1DskZ2-(ff^GvuCC z1_X508VJeZgTiDMg_+iI$)_@-B$z2Eol2O=Zi#PTA4o4%a5b``Dh8m}3ip}^{xcp0 z&eFau<$H2VnTz9?*7eftKU75*>KWFIzB^N=rS+3Ll~6JdIzXf)rm zJ!JZ&7cMV$c><>;v1hvgGOs3nC&52S`zXb5#{>ft#@k}a7BT~DS zp;gTmC|s+sMPZk`5DepjM|6RwS)L7pQmLfnjLd{(B^%j|LPmx|_p9TuIZVSMVQgmN zAot1iEa)vB?`fuhVwB*~ZMPm_cyuLUhuYfN1`3pH<_C31!}V%w4W~%)GMvkw1jyuF z4_QaiWtFI9dwU<`iLDU)qplfA1CSCfGp#%s%?W!^GTbgWqii;sf^(1?D-tOQJN_tgjWqx$C~=ZX z*z*RH_bqxUFMz1g1187lqN zq`gG4f)Rhd#K%F|%w~&Dk?Vdi0=!tCq>K*Br#+pWxwdxlPnbIcuUqVm%rUu|D7_<@Za%_L?G{xDIiG?;749j7ZMa^(d(s%lwp$|0}70#ne; z#z%~}&8i_`nMdA~*vqpj8PSyVxa7bMBgqONeCcvvV}%{Lrltm{)zt>`KA;u+1c`LB zE0zAH(I;BVjFb|0onJy1;bSp~xELf1@%v!9@32!82UHImGxfpOQZh-DAxVGZiUMdT&JJu=Nv|_x4~bMXI-NCig5jyBKYR- z1q8c0WiR366y9fW2}3fm&Rkdjnl)|heXZHvHacR7<)|h(cs6#^R;5tCcN)_}=S~$e zjrsHAOB0aXHRdmjjydp0!3%^Gw#lAdVXyl<@U=$Ci6eQgcI!<7xY#$a!?8hi-yxuz zNIw|rDyad>&^k-W)G2!Hmtoir~5d-3?2m@Pst~jBeWSRSEi6m4ZhwbK_>iXzB*{4o3detDxZ_- z++ETf1bhm}Tmmn{dDTj5MOb;Lt-!OLW@tQIAv`J0UaykJA*KLw)+FOxE$-qpsSVLX zoH3`By~^zt6YsTKi;Pp$G2V_9zZtIc8H@+leCDyivwbTyn$=i7 z$@>01Lh|*zHM6WT_1(uNd{5MFeOgml!S|b#mU~dC_B79s6O@7srLl5wmSOtQa+W1> z)hmUR4;NhP1pU}9iMBXZOy;HuDsh#YS!;|eiDCKON9|f+c#0y%&b2{+#pGdVZ==6TCFPL- z6o92K-bN~)$lP72bLjr?+)3ep+z3O*G8csW;j&l8XC)l4nR1Dz?;eL2^46?w*aHc z^F?J^Lw!-K34Nu%R$^&UwV$Nx3Mh-$75TIZr4%3JLk*cVOvc`U4hS6CF-@Lgav<&s zv9_>jNA23E@HBXKZiJ!e6B@v2grfUux?MQenNgN{yJHem>y)q39w|WKHWj@>Acx%J zaIqj{E32KR9Xs}Zv}wm-k3`+4NR-MnDe{ESD$_}xA#fckpgygH?egZ0{K6T0v|FkD z!Y#2jkm=;9!qyD=OmYUF=_xp%Zs5d+pT*Om%%S|n;Y)=~Q~tT$!h^N>7y6LeR5yHC ztuHv;!vDwLb(#A52y}bR-_+((O;xw|ZEx?zHrL%NF83;(biJ>k%(}Y!M6?K> z(jjh2KU|Wvz*z2S@7sZs`#^uTr6afX?5^JKfgU|1jAzp(_^(uhf`_l*`gwgKw}EIU z?QiD*uIGD&OHGU@BN|$#8;bDe@)aNC0JvgzwPmMvUqkzEP3)n;onjEwF=A^=z$1Yjnjxo=P8b{iw|HRA`MY{vs`T|jp~yn zM+E?Sqw)ceT2lbb)Edib36v5^6(lw6k#^9~V%i@V*G5*$B*hO+kxE{5tK2+!zDtz9 zlb0wnL7Kg@!_SZ;Q@ji&UNzQtHe(-x6#)XzZ@tn3r*3JA{e$gkyfTsAft0%v>7AT% zalAf}-i?&|66rmh^8EPDM0zh$9!#VgIOWCh4=2+5k#aPVKENr-vhj{Y+Ac@hDg0K5 z#JS^xc$tSpgr2wOi^K)v&*Ehv5?{dYZ6fjZ@&ACAMM!)dzjuhl#p74;vIL25;&&+$ zB@em`JSg#(irIM0ngSg%YswlU?Hkq z4;#N@o=H~X{!M={Y6*|-tCJs{K9Ln2-B(ME?lC`q?k%IDql_pN7_3S9xYF>TU!VM- z_2IMVpkFUF=uUtC4b+~*s70wF!xkCBQsWLxwznlsX4rHBDK={Q z9v<{JCqHN{RVX^>Z+2y($^+G;%gIPIIfi%G4R>w zy%zYCpwWRM@#EF>c`6+tp{Hcnw3d@itCVs?UdX$*TJ9wlbHG)0Yj1mx@B{!2TX+bD zMhXSrax`~D58~)4KHwh2aKgy0_#Ebt;6EC{oO1p2dl*U{BKvXDF!I`w+H#at-Ls)5 zBDE5VPyM}FgkogdT_MyFCqJk^h`!-UU;6v27WK3B4ZNij@9tI5-bH*|Tn*A$qESO_ zs0JFgtU!YTJ@y)vBj~*T>h>#rcR8#$kht>-Y5S02PytC;=;PxBdzPs zMIZs~4n(o_zd8X&B@`PKZhgy_#s%FnS%w8!5l)ZYtopS1qzkhI@om`n!*l z_>Zss$gc^^^EVWcI<;GuPYWYHc^M6JUw$Rtf6<@y)U4yh%R^>4i##wMX{9HPd5Jxx!%o+9tqs**k(&g73EWEfKv3upCx zvmOy;bPC-G&Y%rKTxrd)ZiiTS%g8L~3ZCm3&7UtIcCarPyfup*XgjB+H}I)tRdhW( z!>IcU!941ljKmbFtl@sy>Td1sIqMG=FT8cj#-wosuCO4}?{DwS!B1K9`t}oD-MzUd z9gZ4u28tG_L+V2`Bhics=C6&AMv^1nedz{yU#bGTPY-RO?yt#t`7@vsU^^7!5iQyNjs?aA{@n#-ft(2D2{V%PL(BjL=q36FjW@mP(>LDY zW#k5f^`J_0m=xMwRbIKh3wnb=Azjmtr4)}K+UwKyBGeHe+90CKz+uxCs4Y@kM%QVB z7d_`rkgIyih>`f4Uq%rLbn}&=hy-xZU!ofyGP?0U~fsiEZNZ>-McLuD3Bqt)qC7_(|z_0VVgMwwc2TQsMT#Q!N0;PQbbq(en%#K z!(PG5itg%Ye42FshH2{rZx*w;z^`~Uy=}Y4Z18gb)82&x68BT=);`w(-(7Gr(@oYrP=wz}O7^r# z!z8q~8yc)+PSDrU-A@|$?Z8o3=dA{&LQ@#3?I(H>KZK?uW}dXtpBw z%jp~~cG`y77Y;g2wL1=KY43MVXZt8l5!Zp7ebDvq07k6o!zl(H3vhVoM&00)Fx>Y3 zDm2hVg}3zeb+-Z>uz+BP2EzLZAlJ)A5vQq^mK;LXk?C$*4S}OG*VzKi3Z_3adN%u# z&^YGMgC;9b%2w_)Ub!(B4wLdvzk(arX6WQs4r11qVb1ib=VZt!G{X`B2ph^jE{AU5!4F|IqqAuSMAEwVybFU9h_Y zYWy8-VxOdieJHm^*lIi71HD*uPe5r`FA$=%VjZnk70_yc!wf=Wstc=H&N^^H%|8;2 z?X;Tmaf7RvF!Z0q<|>;;1jk%&zmv;ib>_~rW6i;=fQW{uO0{BwmI;HV1P1tKleHs4 z6dxHKGBCz1#=C>w)v#|Kn;Lp%>~5^uTCl`y{Ewn=Vypp)%SPu9z z%OM^ycXHuZGlHJX_MHS3g!-_WU1pCc=oMzX*?|%=MSwL;2SBh1uY{cw5o1&+EW|)L zL0Q>WYc+@lyi?(v)JwCUBl7`7-CzjwzCv@KWd2PxOjClY&@8AA(R&aVNqZP6LW>^@ zEI@le4NwCr0H2S4E1fNEPf!e3+O*gXi<(~Tw6^!Q4s>LDJI-=1BtYm#3_HohZ&Lsy zN-LrnHLEQzW3(kfLO3JvXRc}y%3$~+M&28&@jS& z(tX=HlYP0CeF6n!(m!d>Nq`47kWK>ACSWWb;^qH^Nia*M5LfHgn1miq4Ti z$ilH(Bo6Xzszfv(AMCazI~jc>1HS!ly*a`imQ4Z0#J0jH1%^UGl!sOnjbi2}mt_;Y z)8s!07{w?=1WW+yrY23Hy$AzY1o(_=j|lVu=b)SSaF_W2@C?|AunEAyaW5vf7ri8a z03jSYmiD&8e35&~K=BBvVhae*`BQ*7Qiewgg|P}SbSz>ZkU(E-ee~9a7GdDU6qY_x zWnhQ6&A^%xEC|qw8ue3HpPBA4oN`iElVCfa8XfYEf<$nh0;4K+O;Dgn60`|qr27eM z&eRcoC^QodZlsxe(bc^}cG@8HAGK-}2kc{smJmxcwp3y;oh7kjO6G_*O(KTO9C>aB zT=(tbJH5Lyrhh?h31+s$1^2_H%|KTh9NGx_#|8ofafR`bEO3~ais7XKHFYZl`G!JSl2nV@O0rgB)v%wfG?9gd_74 z84>vvI(G>CItohxdD?@}Nb@am^y8#pQJjAC0-~K6N%4SX!ciq92W>J2G}tiDrHhFN z&~NNkSUSWPQq?TWp-%W>fBi^pq5s!3KDx2AOKhgU;mN#YLXb(7!m8+H@NH{xXrGcy zs^n6qQg5dDr8EtOu>j7=`tL33wUo}xlOYMW(BYp0{TV_OpNivSzBjj6a6+`S%sPsL z6eOErH`JbBMFvYGUuwz^DSVT#+zs*y9GwPwTSH%bZnO26+_#`#%d!m9>8Eeh#nvqP zyMYC=P26T=zqM2aRshsJGruR>6L9{qp=qo7xLZ%bWBSQ;Jw052)JXqhPb)mUs-vYE zKm9I(-f^*825Pp!*NX6;i^hGabc9eHqZ2zkp_o|Y*(3$~2!K!MMhE>!`SFj&A4myT zz6&5IC$?TVqQ6-X9Pl@!Vc<^=10nET>Fj#PSUneiY&}zfBM5aBCtC1uIL{VRgW~W} zm#C;6e1?u0H(;+$-e!ee7AwjRC;Pwgb zGhl&!rIu{adr)U`9yrBU$3Km7rsOMj)8%!>^0%?D8;n=UDXk|K|ysc0hJ8wc@QERk|vO^8&Zg_exe=c z7%#TEsw#@$vlgw@j;cEfQG?U>DeQTcVES; z5F=G3N~3S-fw zT4o2NLobFJ9a%bYWiHb|lgKbMV&(mjbr-ZwTMApR!ten*?zjWjJA)TeLu8$o!ct2S z+jEK60vrTW+zoFTAo#Im-=`fN&~i363OpATLII24_U1U(M~V>=f7QHV%>JkyG$iHH zKmv%SN8H3BJ`Kt9_Cg*)(hy!6%4voME-DazAsnO9VoDX7QENpqt86kC84MUazNT(} zX5Gca4OWnqgXckomJ?UM9A1Q%XY$V$mhS?5mk&P2ForoX;QKvLis}1y9Nv9kJJSyX ze(|0#o9xilc>6L5+VM?l~RPY@1zz+`ZCc7vc+8m}z9 zZe*X;0`W4HMox{a5`oboB~|X+^Wl9v);k}{^|l{BOHv2w3S4>g@pTD_TLf~GLJMJ! z_9~ZR){T>7mMEowKebLpZapMVxPh%F<83{e=&h%6Z)))Jm3>$8FJaqwnYNfR$i-e6 zKSolpk77$nU+WiL-3V4L?#|C4fU$s*e zp041!pe;jBUX{a&R(^zaG^FM3K`Rh|r==ku2~^DkCaK zb%22v+@0`>7WkwHJ#cYHXHP$rP)rUI4)ZwdP*tFxFaiTX*G!+v7O0!`Swvt9qpR{1 z5x~0TxRTPj7U$ek8LupZBh$lty%TB)Hmwi2`<`Ttz$kMnd4UB&>auqn2P3jin z?>_agUOjwSJ-F_fDbSSA_jxFAADnuAsJt=%)R1>6{8^oUg}|F=>7IMiS_cng&@6+L zyQ>wG*gGRBADk1puHA^;wBA_}D3<6}`Shb8|&=bS7dk zBCg|tb$!_?gS`A-5`|PFM)ma2IbN)WK;z$Y1S#c_%!eUjwLi?jy6Gx2w9X-kX z{;c`>w?0k3CMl0K&}@O zavYE4qDDRA}mmiiPWg!52p}IN*@-#;urL z(r5hK)Vt{f@j1GRx|dboL;~x-+3u~>4}21TMYUhu&sy#b=tDS9c0hkZ=+YN8tKdoZ z%nhW~16*kkGI;4y*|9$Kp-zZ}=;EI@81hSqhJjoS^sDhobcYlIWffk6s?;L8TE#>Yx809pyZ!0ur-Cu;ax+Q3VQ~h=V?RLx9g6rTUe2$YBM&3$#l%;M@mAsT`Y_btpa|D_7Dw5> zK3r-;zYT8Yp+(o9!{uSN>nu6#uM~aasf)!D8e<4kM56AaLKl&&I_OW;y%UX27Gt1q z_xhWSiOpExyxKgU_u(-68P9b&a&tGx^KCg*P`R zYO&Vq_Yb{Xc=N$T?I=9C4nb}~ffL^R4+(oN1n4~ldp>GSE`Y0PsyTTZJh|SEy6r`S z$ve3AE=9GMP;GMAXua(WB~yhrf0Jvj97--n%_|r;?*wjwjx3Y^w6Ic; zpew8-Mq0JDdl<>qJi1tgr`&EFE1y;nKaq7r%|Z8^$Lf2xQYgla?+-Kb2gEGL(F?DslB24cS(;|q3z>le2K zRNV9}$V2kx1*-HkCB}f7)$CxPIM9NMKp$|&Db^a|>7waj*liQetZ|4TpahYVPbs2x zS!GmZ3T!lWUVRz`{rE1cd_*V)3h)3$LU`hL9uVOzhF(t7}Oaa)`@YnrqucpttrHR2yW3`g+ihZYYJB+(pMEdNPvbu1l@M# zG6Xr~eKka2)NUKvtJgch4H;V4%p=mW^omtsbeK)XC)EW1Mzea@hDc+_BMh|xYLJnr zp>}%WB76nvgC%OiCF*=7Sf|xbBkICs>V0K+Sb?t5I0GoP#j4qSBgIWPlM zE-(XcS|1gQ`UvSs9VHv2X51*I5nu$18mGA3hsm94V|wuUH|i6$_38Tj3qzMjF&Fy} z=U?tCG=gh;<6xpLQ$Lg}!@=UpDcnny7vA_tqHYup`Mnz|d9D-umv_3O6CgDATUd+s z^ZbHygVtY=XT5k%=WA`<@+?3RgVZ)54n)kWPi*zFuBU%x9*3)YX-MlW*Qg~-(_3o& z`a1?PB6^5j5rjf)rXsv}HMhl#4nmHwT_!1D2np<4S&#A%TI;BgZR z6>fxpgb!d(^k79RQ1_QN-}FJ$!1by)OvvEzM6w$CVHI#Hqgrp4rA;d38$bvLzi&N$ z?e+wt;nqPMFs{vqckKK#qrkyYgk!t5KC%1Yo*^7f_Z|ch!1uNE{iRV3YD3cYYsV9I zg+14vH@^<8WlyW8)joNqQlZ$aaSi&S^sURVcInj2r+M32&p|@pZ zn4=d+N{0ybwA!lBEHOeL46xl&u$X0GS;Oric~PXo{&#;Bef<`S;UsClW)rw{ zbmM|DrX__`bdU=8dlH{-Lfr~a%B1^96thb9Qz&XuFQfYzmv^##m9ro0tESK&vA!y3 z^Hw6u+cmF6aS788Dv{dAY7(gpVma{fx-hZ>x@Z+7ronNVVmgh|f*67(+Q=g$!?~T( z>2y5`++Q-3Tv`}8jS|c9Nr%2b=b&=bUT&`#O5Rx*c^Y5t$|tMnOZ8xK<*0qPy=o}A zx-jx3s%s6U*Nh&q?-@#_d1+z&MfarrCS?>1{8~&$7L92{I2B9hdZh*olP&UcUSw@vG5;cp>5sl&GOZxU^A;>gE zR(}tzS=3-eeoJ>wHW4gGeUB`shwkrMg*XgCiH=r)Lq|trOt$+WWVfcbcI2RUXLh^< z=i<75f4sf7ufL%k(U#))7kwi0cDE|(rbI_0)tJ8UM*-Y$x}4PT2Z9k&y$uMIiW`gM zc)fA3L7Jb*P_ed#$uvjR2w+` z3uwN#W=yR0H^Z_Fi4~D;sBMB|6e3?>2w?>Gx4^g^1d~~-Vz3PXg;)-#M^j2k1v;%Q zOfW}g!xuAD8;;UxV&}oeA@H#ysYLo<{?aI2+K;TE1I7!ZbZ!Byf1m$+L zIFDrQ1qAZ;<)a^IUw)rP^S^RiR=W)i!(GHe=+)C1Zq4RX3hd0>QjC0jq@DRnJGrur z`AS>A!VS4p^X^2%07ca_7wMBhOw(RMMt|Qg)eXH9xyEjwg%RBE$=#QA`yX{%j{Y|I zf+NTrHTtDCXpZmQbF}S=7a^*zLneeVZ9Y8HF(>SfL^L{A;}wy zaLOi|7Lg{o^eDu8yrzSFBOsf?g?GY1>f~qnt z?OQ=YlxIv`Vk#=(9dz@&9B_Vv6maU=>fK_DJbDqvR7nrTy&+XR|r7? zZSy@3fe^farMwoJ1t>U1UmSfYnicH3Mpy z`xYlQ@ZMz{do(}{el@SKV@mtVcKRpK@IQM8Z(H@ z#odQ(P&O13|6xkZr^IY{3lEr+_WD^cBwn=(9zt-H#fjScVb9A>Ey0kLZ5Y+RL{ zI*y{XhiezW#6)IfTE=wxNsZx;lWQajqq>h);=whplMFqW>p#$`l~tWhwv9Ka-HdJ= zjp=3^%vEXk;HWx?E_Qxo2Vr5Ww<2{VY>YO`5al?|^BvwOzQbvV*KjR1HEBnhVNJ-@ zZcW*Ior6Hd`-I{EH5(clq+2=2f?*%(MMQ}`eF)YA>s1qi!?j6)JHvDeXFBMie$CeuU$z53fSdm6>!btx?~3;MgGGXmDQag&Xi-dyfii<>Ue$n3+d4CUX(@h!!-2 zq53#Yo11l}{)+FcZ@p=OJ0)CsD-A}WoVfD3v7CSIyzX_bhw`F2|2(*nH`vx4lk=hD z!eMk+)hCzup=X^I(A_2|y1uu6RwDV7{3t9h?PZH_*`Mv{?;qHS@GdZsbLw8Nh)NU`Rd zqamE6Lxd9~A*T4kX+B{JDhs)a^qpiVYRR@h<4Kf~VrIHq!SUpE#huGvM_~%@sa^V? z^y0ic8QjrIIqsf@y+8Nv0`&Y=7XM9?VfNC&v2%mDuyO1eB!Smzx#Kq}k7eUaH5HCCYm z9xP9)Zc6`J0;fhzeF*C7BJQtqv`v2(Rj^|R8Km;Z=*yTS0ip<-GmFuCnf&k}VdF** z;C?~C{U-lBsbOCh{l7TGfgO-69%m%zkf0xHdf(Y*PHn)>4G1zVBcp_=Hr904845J* zIT$pMH-SDNMi(l2#KZ8c=7Zbe&;SCejtq#0A;qM&Sh2$DmB|z#X>9dTj9Wak|D^s- z!mbiDxymgkH2Gti2l|1$`?37|RF$kyzdC?)bWlBrqFCCc5{85Zt?Rhb%5a2RjB? zSoH6X;B&iLh3uqZ{BLw=jupVI?#QUssdU;c&AbK)^0u&XEu+FIw{W4RiTO2>B8~0G zML@%59Sxc`7c_|c7y}MJ@xa4Rt^`1TaF2%1wkv|qHXlBPKG^}#HXlF*pqU4spB90S zhdMF%yq*UiJc+>vPp$+$U%1C&(6%c>Ar!v?971UiiDm+$s3N!-QBYeCzLjm;juVU% zHCSG6Wsj1#;RIqpw&0pp8Z9oIL-V2IL*~k$@&|i0*4*i1O@UE6iwewfxpm?o&7D3# z3ZYmY)Y-$y9N)|M8; z%N0tz1b3~FVr4zujJt#2t$0CAtOpvkKzcn&zRnZPxNC)5WZS;+S922waCdP0H8?xd zd6(S8r+|JfkXK=2W{uUvBY4mXUo|{fJPwuB#26kNgo_RyEFG`Bp0V);kos@=_(=EO zTE)G)QtAB$M-xo<8)NsIV)vV4_qWIHw>bCZwvs4X*^wE3G?VGmxUX)pBa6IOc4C~R z5U=Yki~O{kjJ zZQ~4wytzQXs;)QWLQCiN;&r_-7pl7jb-gVfyZkThE_TpmfLdJ!sMX))qrTX}Ks$Kk ztqpnPE{}5P`v<-4XEaE03HIJ4#c{uR2UEGQ2RJzgRge?n!X8q^3+Iz;e9Olym|Ba1 zsU-%c{(LauQ5;NobY)=bHtV~W5`uVOihqm?CMrB*Fi~L-OyWjrVXDAXNXu#b0s*)Z zTFz@7j##vuoRk8U@Mlh>8^P$Sy0{6K@TdBT76DMt(XjI2R&Ff%mM)3g>zwNw<54Vc z46du0T#!hQK7Dz9;^+rcRTMPsTu#Ctkk-{y{v#9Vril3<`n@1Zw(BG7&4U@0(m+%W zqPVC-FsvaYYY0a;gfvGlfbE&0m5ZEZ>WlIsY{CN^r>B3rhonJAI6Wq-UImeZJW9k? z=u{ot9^l6q(g|jCD6J#m6(2{0bWJ6NkX~7rwuN}#@}@3Mah?q3g|&6#Pa{)EofFeS z<|%S!GPk@u^U(6?7le;SW*%C;subqXOf`|*PB7veox z1@;Y!jOS4m7;ELpC45R$?m+@j5IG&rbQ(xg1%SS6iSmmcD@S+Z!&x-8S zq;AnU!z*)QRw68Dw|N?Vv;LxoioL1C3s>cDt3(Iz$~6zzS6#{5%rdhJ`lms~ox}|# zWA&ny!K^#C7I{;#y6_9zet9$l7*=L|=HYsV~iqiKR#y|2AD15td1ex`rP zrof1&dNG}#4$=-l#nTktj+9{Vyl$tX(6BT3h|VSer}^DvyT>v(ei*A7YW^9tzj@~E zGhcQkZF^&HEID?HNp&Snd&33?NLgdd`j3N!p`pno!8Wg_{sAb-psd`3NO^@9zO+eZ z51z3rp)G3q>+h^L`Ze!cb%!Q2Pg=#KNl|yE_SP${o_4FRzS7!_=39rZoGgAhcuLtN zU!Pvc6zp7fY7t1yg3)KD>kzsB{L~7V7;jHh<9x;C4T)-;W}VuYXq(!Cr@OcU=;dyz z0P1&<43*PI`SDBHsfUS{!HF1Dw|0LEBt9mK)f_Qn`U#M>YS26o%qKNzTgZ~#BD;zI zu_gnC$%wG6$H`t4{qHV8ivFE@XP6Abl|{XPX!p<|iWyZ1I!ACtZ5ca1kgFMe zwss{{a<#i37{5Tz;1q<+R2Or#_phMv+-aPAB6ZzdXBo|!@9MX$ss7evbVM(Pe_f)G z!hQ;Rr2cFhQLn6t5?uE06iDf@I7c zgUH!)l4DtMQVMDg{IoOE50KrP5=EG$Azp0P&iP=KA32?ql$)V6B6lbaqGLBb`?c+ls?8=+R>9R5#(CAF&m5kLh!PsL`aP>|L8gUz+uAB22^1qAzW&FFam;aU6^SXecqFXH7( zw6tDk7|laI)0wO&IJ=c7r{GG9$}U0;Z5Zs!F00?xgti1X6kkOeKP(TU@M*J)sucT~ zd}C;t{!1+yJJXH}Um=A54K60z=W#lJrV(ex3tTVFLaQo@4h8!;dLOYu=W^9JVl{nz zqW111b{gj~Xa&~kMAwkZ$sHOS_sHU{82Yev>F#)E`9Qb*C%ayw`W6;y&;o4cL(Tfk;! z9Qfm$Z^H8Xm)mtV!X*IlLI*<5qIcGH16Wh%`jR*)871A6d>VVxrkaW{Q z6##z_sKc0m>ppL5!GrerE(yfHF1Vo<`y{r>+9TL7pSeW&az9a9?S0aojiY5p zRx+jX)KUdKPj4wFr+s?{vI1z)m8vK6Aaq6G)H_`oK3Zmu0qmU|-?=oDXHotdFOhrn z7(loQ|215d{J7`ib5g^WKwQGd=S{bq`1p{YbEkcL!Z+$v1&DU-`YQYQQ{w>mdS?mXE(Xl<%=3|PA$`vqsizY4)Y zoT8OVY{IZFv`b;6z%s)n|6`A2H0g?edB#`5bl zCN0$G6QL8qO%rnku1Fe_Jxbw7CzdAef%9UPf?NkJ|uwP&&2nYcYBdTYLT}%hE=p?9XI@HT2~d89!1e7u2Ku&Yq9vn zl;=Y&L0WhE?@#RYU-=Hur(=D&!I7h{$66;|OIf&_)@S=I4wh>1PgINeWHqG!sfT8q z@^Btc{)~}uTt9gm=%c^s`{wFL=IZaw)!&(`=w^FKNBz~`I(wP1m)-XAi}uo?LSM2^ zU$>VId=Y9?|9#C~zGW}JZZ9v{%fGOf|JGjqxxI8$h75n+OelEUz=E1J9chrKC)FTON2X+zU|W3vz*Hm&!6jJ zn!q`DIg{Gl;HIwrkq#V$?8+Pm{p&JJf#R7!L)T(zZz@d--VY{lni1^AeqprmFCA+@ z%GhCg;?hmo`YS6%bqL<1Pyr;a@OW+6YX=9ZQhn(GBe@Hz3NNQ!Vo5z`dLn*K_bP`c zX-IM68Uz~wF9Ii^%HX`bG_eTzj;F2%WLG9a5ZtMop`6CE=Q6n^i$=kooN)M)xtJ^d zFIX5GFm*@yqySaLzVwxY?lho#7%?OWR^Vf4ymV^Be23kZ=pDgMz*}qrly+7M4XcLZ zvy`X8Ej0`LQS}69+Z-TmZ!D-$296x`IYXpuGuQ0hVJi@`8lRQr!Ln*tY8NG%Ye+K# z69l~CrxufqVDwqea==OLUrWxW$awnCAt-MNq>E{(-Va9k#GX3wQu#Pe)pW6jak&3f zrtk3CfeiHT%{>Dr+u`ff-RU7I7sUsO_qAl8m%&gz;r#V(B2Mr5>Tr&h2U(dp?!ECk#hl7`r(3;u9|o z^u0daWSac22(cu*)q2cuLELdB^w3g!i1%&0h0xDD;xC53nf_6+Ud z4uR5E%n#d`Wg(7_dqK0r0`4E_6<-Q^!DGM+ZpB#sViRhbaYxvovLNgy1R`&J>O-f& zqoygkv3w~cRCQ%qqSw^7TI_VI|FGyyA_YD~EGtV6P!s?OgNhLBaAvS~)hbdsa5u+H zr!mXIcu>6^G!LY&7HTTmNn~X~`x{iQZCKUz@QPzA?_lP7E|{NFv+BWoK-I)2QE*w% zS^oXt&Q9>edvG?PZV(C8CGr!+7scDbwN(q?ate_m z3-vUUT{;(YDmj#l%BTk34>l^T5i#kU>I4f@4pj7YWzN8?+uQ*O5L4C`vLuk{Kkf`+ zW3U9>=9ZEyle?RA!lTdUu0yazC7TAHvg7Sa5z$-9zUgIP^-y-kNB zFAlZODmJ%Wgn&7f^~G9ONpe6`6c&dA5Q2u4_`%nBSC0);Uf@PGD);*-WjlHktDqde ziD5?c{h(Ed5h&4RoL$OJRfG2KcqF%T^x47O(xKdcxEVz1IS{Gmr<)V$8XR#70hvZk zn{wDPTXOfUAA32K9(|?piC1OnD3Yn7GlsLo{VS4PngUH)D&+D*&b3414P8Q8cRMvr z8%)R?YNzN`ktDA~uf7lAkCGA;AXs@63+r=82#P-6rs$a_Tg7OfP#s>bifG*@y#gbR zF43%7vyL^1(xDf*fApmT3`nzi6pm>?n@1yccSVS#?udU^3rQ?S33(74;6mt9p>OsH zlldM{h0>%`)2#wz(~?X>s|`tY=q8OWPEw*D1v?fX6vFjDVC=RjAoz703JqRhZ9wQ} z>DhCa5iye79M*sd+z8YMH`tP^N+TQ63*#4AF(7|(!gAiv)94GDOF+z2q^aOUMzaO`|;>efUX@`0vDpP$-* zt8IxejoG*hvvCiVy%%L`(xWe2qE_5gt~?!YQwmT0X9UjiTX0bD*^OThq_RdBF;%cDNN!ZCMDm>-$k-QenOo6%Q2eBQ~P3Ct@u_VMqLK5g= zkJ9$OQ-<}YCwF(B=$1p_vYpa1%e;z^C2tCJ*TpiJWAJdhG@l|DtMTE^CwX6!x6uAB zxMcx?3eKg4CJgQ9`Dt>soSI9EDTT#^^j5N%L{c@2$r1O{m^5eE6zAxF2Q!4D^qCWx zK}=E6Rf9$24O@ecjX8x@gPqPRME2{ z_?t|{x4t|qW}J$T#`ckt@%b1Dm2ZcGPmGk#eH?+y6xXPJGKTl4mj74n7K(lt)Qt3j zed#~Z2jxbWRk7Vg+EZwb2V}n~PRyd{zY>o_LZu@bl0EMfq)gH|h{(zc2016d>>6T< ztY9Z8!w$LbvklEkrnJJU2*RsOYDwkgMnY(V#G>#uHLG-h1|_U>LEUwOEz9TDSNU?V zHx$wf^$$WhkT(`HbS(+%^#;y@co$)G)#$+D?FXQSLQ*4@9-Jv!c5E{NQ~pq_60ZDM zY}vAd*+a@zqg=?JFEtQ`8hC1@UdpRspvn>-k~5y@>Fvz~83>!o%Cbf=w?PGyW< zkP{iueaTKmr~K72d>iV3PPqG?NOSv!^B5d28gN2%v#LQ;Anpu~}4i}ma>UF_(fEw)b zxsF^lbc@#~c6Q_*>dZZqow@-`N%iQn)2jgI=dxhE>GpXzd^6a$;Ijvg?*$ipt&7%OPC;NyI@ z_a4cy5A4Q#)xj~R3t86zYvhz6@x@_wnXlE&tqjApir+@#a8O|#F8ox8`?e(&!|k05 zV4l))pm{YMcHAO~T3M(j@*Z_#pNY7UKa-i@V__{0>dQiKkGrV_(QZH;b(e#618lo= z429Kn{1N(r3M;uwB_+;VyWK(4yR#DyeULo~^msZ~M|rOx(hcTPjc_9+g*C7#brh!R z)RNk&CoUkT=EB8`o_Hab&RfAzw9eK2IQF^Feu`tNGj>1g&(B)z|3kjEghKja?CX@T z2It6z)Sz8A$fPY43}Rj#h1|-7Yhc0{A%8)H>|(jMiHJD-iZc%Ygs^S1HzD~>Y26xd zh)(s8qZ7@n5j%j{xYd23lsgKE1!a7cxDsh~#rjTrol}>HtndfAeOnZx>ZT;mv}jB# zJNmoMYLCY}S6Q~sqcnfL9Tr@LhEMh*5}C2!u)_p!M!>-T>T1|z0SmL$5{O74pzVXi zARx<^wnU-UF>XE|%fA>jDteXM!9MSki}?UbmX4nwR$hwDATWU}hA! zoH2UzLp}50=izsmLbU2y*u}j$MQ{_r-CX>VZCn5(AJXoO6w7hIk!AHLe76 zEcSoS1sVsPvE3CgZ;0UMY%O-t(SbMWkoeth@TyD=cqr)3 z6gxDGucOr2q|pUdm~pU*Pmh^i7#k(}6dzQ5dW9QPyC+IklI?}R-|~ZGo+9;#^o7OB zK%uhSBlG0i&sI(#(9ng!X1sK?)ZCr2T!0ab&ryAzK_`m76dXY?r)ua$vg{pw7m({& zC|qw!grsm?F5Cui^5XRRLFF1D7Ah@x+59<)o#X4weO?3(X z8*79I@pv5L%Qqp&U4lz`!5}wHkispWm=I3p(-NLFhime*g@g#jX1FNa*dd@YzBbeY zM%O(|8qbY-;Se0ZMia!^(<8HTfl0!!dDs3zq21xper@ z7}S|L93w?^!}Qo|Q#-NB)qYKmpc)L{=e2T2Y_0Iy?>gYw$w8u>vd*Px%h^=mr)KSg zKM`;iH@`%^1cROWkR^L7(B9EUGI$R~^A>v_-)s6^)NZKcJ&$DHVI>D6PV3ehRL6Fn zw8T$|6~OCZ6xMBlhl4R#8=z^>?-S6aB{>IQG4Q-w6iS%BtAg z%ozBis0nyQ;9q{O--~lhK6ZTtre_59s7izoRmYymA|xi{PdLGhfg^!Lf&ZSLz83+@ zFI-M1LSWidBMKmqA@P9s;dB-l!&k;%%}(v1deENBa&yrvia^cvpRpoG5qt<8Yq8_B z=xzkYf3GR120cVnUZkxb8tOjLM_YG{_SjHnJ#I_=Nm=Cl3L)pNGXrpu z<(MQEGiBw|#4%ZQsIF&Mw)}=caV}5olII9fNoqtRB)=p%gD?`TJMkWdK*tzdnR&N+ zFuDi`YAC*s83HzvQQQ%7b27Ob7wq7gl=P$=i0ZdBUMpT18(AD%M~f7nV=H zhWc-EyW$OHoBIYoUE%c>MH8SBicRV127gS)j1eIvsMtoNqTJRVJweM-sdRzXh5Qsj z+*f*hjJI+fHjm{2GiBl`U~Y->yAF$X!&2HVuM5YWQKK5+gJ9OyuzpRwS3KSe-_w`r z${gGr+_-6kMPYoI-;UF*a(BwBu)^!ut-!HzE!r9ilQ%o0y_PRo9oBCva2(_7!L!(+ zVAic6Ts}vxsbtp$ZX;L_KV$s9yfzUcsL<3pdRH?FOD~EbYlp-U0&ZVEkO&ddf2suq z_Q6u##^@c6zJ@44shUgFI8K5RhF|`S5^|;@ov# z{J0|L`Xi5`?DC0v2+OSMOqLKfGoz~Ei5B4miavI$RQj%%qL{Wo62!SeY~Q1WL$3`@ zRIMt95ge=(uT%veUjtpLVIZ>*S!bB^tf+(*pFt%CNh9FnVA>5=+-Q867W^jV zJjTn+c&l;E_=@tdfd`^Lm0Z}gABOreWg$5h%~@k zOP&tF?mg~Or)#}rBXgt&YqoGB!3xb|srfps^u{MAeIC#t! zE9I!;SYHZeyU{d$3!P5G(cO>hX1^S)#ZeCIWwm4fTtN{Ip2<~DUY|%K_`&%s(%jYV zO2I^gd^Lz;lfo8B7|+ICqX9MnprXhRL^6-QSa-bAT-{=>qSK}2spzF3TB70y0k7Ad z*H}D04sAHPb#ov)tz2`|Sp6b$j2N&l1};EgPwnr#WLmUH=-pfaBK0P7~{ z0TPvk6Q9MG=LEm})Atr0|W>qGQk2G1eOP&d5TfW+rduLsWX;KT^`I1zF8GKAsHGq}x z(MLq6@%%~qx4{Y)ylzN90SW#zw=*>c-W8jU_}h|!pI02^Tc9Hz$714(Pb;3VGf;|F zI43Df(25W9DFuQ8jQw+h!^oY>)geM>?T$m^=ckq-fXYD|fL;&6(1kC@LYKsEOCDH> z-U|M)3<)T;w2iKy+*$?ND2l?YlQ!7s1!z~Vip8^@;&HHbVUR=3;&!ItMjBBT1|AqD zoT@SN--k0y=>+gBcNHQ~;oDnU9<1uH*_{+Qe&1WJM%(qFp1UaeN;&5JtF-2fDjF+T z>$aI=)B@r$51Mg(P)rX5J`ozAgA7l9S)r9m=fesreiXB37O$fciUBu@{yaFcop$=k zWn`#^(mz-I2He~!f+8Hf}m#dqbX zTM&tYb`S!I%M9iEr86sFujBzxNgZ*)w(6X4@pV~<+d=1CXv*!ZGHPf+v=qo#9_1#; z;R5PR@4%>X%hLmqtW&^R9;$iDd9>`-%`Sm?IxB;$tZ*YF1PO9Z638dJ$7UK=p+7@q z97UIDN*suAPQ#wQvCYsJ^D%iMXaNpG{VWIiMoInLHhrUv(x>zkYdeQ*Ixz7brjRY8*fisVK{rG^+;`l3dn@j_MfOHwrX zC%ns1s%SZ5nCSWl#RRX4B2fe55cvtJgQ$d3>}yymGJ*e*j7n$+Q{UjAqN95hcOPQb z3!eAsB-&I2hYNHSLj(D?a);5$(Wxn-tDt!QLh(vZ@i>XZi^4DJ=RYxNPNu=@C+MR955M3ZV|= zC(llu#!Hy&l8X@QZZRIxxfLOLOL6#k$q20QV*K!2H;ZDEqd@!$qZ_+;-4-k!>4S?i zx#)F;62sRCN4z#-6&It*d5<+z=qHTu6Edo z(K~CB2>teVbZ7N4>f8RQ0&w$kOR<4L^e7kk%Ga(-Dq)aY}eq+|_LibE0Lz?== z5qUDYLomu?z@tsU9x&f}&qKQ95J3(;;B3nT9B^*W({6YH3^>uYEI4p%g5#sju^SCdi{c&y?rq?3frWrWBZ}&joe0pUSni=UTgS+i}dTE4Q$f zQmxe;&)wZZQFmG-;0_%Ug%htu+-PC)^Ylc(1N^n>Knpya>;D^Kl%Rf6yp->^-QLIB zGx=NMEHjupRSbYKr!*)QpFYNc5_AiN3lR&}1Ihl5(;$dn?7pJ2J!wR{#Lo&sT5eLi zLQ_xDH6DAuo=$V*9cS3FpBz)9HBbSQFxSxet3;eV95@K_{vCiP=J( z)6uFZO)m3*`LC3%D*m?9i+jS&u$BUeyxnESUiM0tK;dQ!!0DA>DRkxl6d(cXQLd(j zT%kzbWLk6;W)Sq0(%#ltIU zo>7O>AQDGquwZ9mXv^nbDl6TliUjc>eVQ(219-5RT;mS{39y_-TqMVE*Sc&lN6-j> zoUXzc@#k_@YT@XnY34X6wF<{!PnkKJZ}htsrigAO@c)0DXCvl%z+C0@)qmF)(GPHP z*Zi6H=E?QWTk|k_$+NcomRqJo)K%93SY^APZvr;zAmHz+#=fb3|C9RtkLvet)bF>| zZ}drh5xMcb99ifNTOiUQSLsty&hy)y>!u25032)IWH1lT{fF*jvRV#ZWgJ!f1SzS*Ka4k5m~j?+wY~xM@o60xA+_B z`GNQ5XTb=WuTD82pTWGZ)?dAE!~%RHy5Hn?zabC#zOB)|%Ip7E4%Dxt|3f1k2I!me zaeqzT_eY-$swS>Uv_81KwfVo^k_G$6FrYwgSKpiI>FEz2gv;UFlYEKzRy|#{bgy2i zdyOA680o=|k)BfrGXp*Cohp)Yc<1`t!Rl@f>s6!mxT|+NtoMPa%W>xgQl3VegXW1F z5)+FPNKP_Qm1zC$tSok7h`JPyEqgbIK)xd}u_=K(!xO2*J@o#C?C8ZT`;6|TEW?ET zq@nx@|L?BgV|s$|@y@mW@YM(dtc%Xsml* zn{91vcQ%IUZ4Zl_@BesF++0#;7Gr%ZaI*dyooqIg_GPSB+KK(%csJ5sp()xZS+c*I z%(ke!;X82s;7;UFLEi9PxPD+a@?Fq0z`k4<b;6Av=h0> z8xp(k!S&I5kx-2QNYTcSdc8gQ@T^;jtS$G#dW#0U^*iydYqe0^5n z2iY&fTTb}+89C>2h8RW>N#)dOeY>DYQ@8XDr+v>>fU;v@-}qmU`;~m|@&3$IG zS-&~l7^WNjv27?cj1&)J`vdQ-^wy;(W4T>G za_GH!Z}(x7i;Q9>!$@n896H&39Kjck%W)k{?fNZ%N^A?>kjMH^PfY@qW{kwygJ%7d zpz=$+zf#a_^u~0s`@~6iP>t(1gljhVlcgYJ1Hw1!nNX0BpSL`w{C8En^x1~Pjw*Du zAM1 zF&b80b5aBWx%)DvT6}H(IJlbPl8b!rF#SI+{|UDvK1W`kJgQ@6SOts3$-{34A;k6L zxo~kS0?IrAFZ{&^k3NAw6pIh-dgA%qVi}yH2;VPm6laoWX8icFz&D@9JryJcUgezq0Fx#9ArEAFsXM2YN)x>I+ z{OQm)Q*ln7o;NmF$C9-j1kSR^dn{P%tP1q_0P>Iv2kAU#vw*yb(;VR9;-l?3Kw?35 z!Q~p#7fO%(!~&HLz{p+D4mLa34E%Z| zSM|>FL?d+UFnZmOP+Q9q=|;&Hkp;V%erI)}3gP9m@JXv0eW7LSVml)v$G8>s7}WT>-8>8$C;+>7H?&8&im-eoO&xATca; z>*;xGpoc1G#?~+eO=ceAL~TK`jo~~4EL}_uGXQiC+IAog)4r9&b?R?3)aQCl%^Y6n z|1xj+;U%NnRhQ^=KAyjc{~Uw#cbxXx=`?`D zUwsI@z}1NsQ2&-@qz!|1pcVRnR!SZQp%WnpvaKXDO{`9Q9A`&a4={0i;sE)iBo`#B z;a$g=EzWhXc~aDBbc-jAptA}>tF`6~kJr18e}Dp1nuW2TVuSmRoTEiL43{_eMcb5jP&F%zeB(R3sjS$mAx2Ro(?40JSTx5OcRY5Xxg$8~Vi!OOp z8jDmh7?VG+^+l>!q{-{W;=eyIA8)YIqUhbAfebD8BZ|~Pauu0al(_qTc=a44h2unB zq6LBavn_j&UzgZ0#DsHo6N4;_S1nlw}~Ivy@%{<#pG z>oRsDn6B1=<U;s;L4Z$a>Y_vo zR$aD*327Y&>6qF(34G9@&sxMEyjbmbl#)?>@oLdWMvHye@y5wyF&QnMMKm%-Q_Jpz z&-NNKOk+B{HAHd|?^E+O8w*E3;a1vlyz+SHZ%!7F*aB}Q^5vDstzVvy-9qxspq_Rm z7$o$r1gC)PJCZ$=bBRWD+5qQ;CG-qa{&+P?K@aTVwavvrypahJ|^y;_()D(4j8he5?A zmx^^!Q*-=sLB(dbNZAD!bwY8WS-KRYPNYRy?^B^XM%>~F=9_DvNQ|TxTWKgcqZ)Rb z2Hwu7hTWypn^XbmPW={-~lM3pa!N-QWdj}BG%;k}Em-cO8>i082psNQFaTzYSdBmr<0r#gt zG>?x9AC%L=p7oZMgkAkCipYB`V7*+=c<7e5uJzYo>GAqzb8w*sHx_PiV;o~tgM^l` z2JzJ+gMGXE2ZzrdezbqbS!cY3+Xg!NBt^k3pGO^DEfDZT{>z;GM@XDQ?~z&~MXNw0Pr{#WL>auoeE_+|{kknea#MEI*ae6o8; zhZd4A9fHlA#xLi*56Z;#4|L*$iI``&Itz;oDI%{B6t{*OJda{bpn%7`-&puPiV2A( z)#1agQ?hgNLgbPY8L0EbUzLhR9C<@)IGEPMRp*l|eDd>UaFSD;aVZ|U0{4R_yRpu3IAWli^P}~0ob(~8KGcb^aM_^{W8o`rf>jfHj#Fu9 znUS(<_%^y86t}7{)82V9tmk0!+lOE-QMvMjZH6`RG|pYIQE_mjlfG9z@WGRa&WKhz z+lNuz0R|qO%pfAbqbD&)gehT2CfEhH-uA&vm?5u3`weLAETU^< z_zSxF!+L$)x-Cvd|0mH; z-q&Wm2(_8mz^?KbZOt>#?@xp8clA?1YYEi3$3)?gdBJojwVB+LCM|?2t%;%^x6>S- zfhsZ$gLenroF&Bpf(QtzN)=w{bTCI3cMfR&nfu z2rd2TV;!94VMC+Ms^}e}*tBU=-T}2A1dWqOYx^N$M2P+XA(3`{IQsmDqZbCU_mXTo zxh8QRK>p$A%PFPq{3mQj2xA+}+Ca?1F8O7}fMzdNt9JN1x~PQg=Gu2wotJauK2pn^qbJat8A zB`D*mD=;hB7*AapIjFc@jhBCAVXqDem;ku|C#^5w_|#6sfTxR>KJ9 z^&5-S*chu}1oQe$MQX(DIz}+B-&~|dTs2C9Ij`&b+l$n=JvOS_O^q!@YHW$s*ivZa z{zGsZ83AZ-LQp+89;XgM4U2V~Iz)P{)L~k;sryN-r9!0AL0%jg4X*XMb;8eN^7FwQ z%ciPFnz%;M&v4pQa5wt*_VU}w&(8pp=fP(oGk7}FC5J}I8$vi-To4^~&U`9Cie5Or zVhwP=+d+J@u#rs!H90>8-vWZYF0UNLs62`?!(FtjauHhr#-+66r5oR%PP29*6H^jpDvIPyRyoC7)EIZ&kM|J%I1>O1A-TZ)vm(h1)fOXE6NtFh%VK5AV#`x$HB zp2^CQIg{!T9op)T7nA)!Bv3l@LBi8tQ7p@Y1gdk>+f5`6~m#CT>V(7ARY3?83gl(zyl4FsF^pEa@dK}EYT^whbRt+*`vZ6OTL=Tfq*8Q zVSPOT@ywiN)q$hMF_me{no+e9*$t8vmf6E*vIKT=-~5-u1-M<@Y>S7lxHF_{}R z?bUNBEm}J3&Bh9O8|OPW1Z~?z(TJD_N(w+K`hzfI?{)-NN(UJe6nO`*LzZm2f40yGM%k7-BaPEO5PN};FmEcELBzee}8JwZVvR}GF z$Vwk!So;GQ_i5k!~g+?W|1Rd?|U$rDQ!GHzZe$SKgYNcsh|>oxJT* za?PdWT0CBtOpRBjS@vlJ?gm&luzCl)Rtb}3)yAbBU?3li3TAbH?8l^^1U1k;)MbBs zIcQcI!jdj=Rk~4%`cjdq?=A`(rZHN<1((zSN}3;+r0$ehZcN?0#3z;WrQ{W1IWYUAq?URLLw$12W%ar;bX|G-%Xzj+$znUAQ2@!Jf@P;8)2 ze&skk6t@ga?Zde-A1wcIuey!h@w_mDgumlQ)c4-k6&l!GmREl{bMsUp`(LGGPWqcOWjD z#Jm6$kCu1C@m0>QA5<_qF;D$!!c@U51D?9915@y1gs059o3hL-;Hk?rD6&bVg@9_- zZME7m_zgznjwOKNpYG=cb4d%{4uPNpEQ3>ICcMZ$b4aKI$HT`_R|I&=jxd4K0vkP6 zg>eG*xW6Fcx+ViXgC>6Ue!qHL@#M28wpWl;mzc(NNj~3UtHo$=T%punWRNks6xS*d z4?+S9g3frL$s>z-!*q^Y`&9up{|gJiwMehP2ES^)T;ZY#TG9N|m-y6sVaGo(PBFK? zVD|3*BRg2w_!6xf@CCrR5|rvog$Y%l@b^pVBDh)t!Mg(S42apcdio#D4DLkuA5GC@ zc=e%q;h_xeUpztnc3P0E6=|UuZ4+F;c8rNAQkCDudS+9>kTV}}X5C$EzrcwQGa(b; zWZhFB%*m3*UURZqWa9l|evxUYU=_mrfPRD7X;@WxZWUhy1I%NriI^YEI!g@R)s0-j zouvTY9j7xemk{ZhA6%Uo;(HXH-`~}>h8m<50#l?zn}RA*J;2(`C!&Yjd+0DAYFC}= zX&>(CY405hGd;t@-NfKkq4~Zd?aTm0$cc@|r!k}+szrX_V@8>I1=~}w8lL+zDljz5 zwEnTPMQWaXEM8Rs*Va`%7HL31I^IeQ>bA8w$cr=^pMrS%@#%1Jw?odh7%$?*2}ofu z0*Va~j_mi1zMS1h1}7Y8NmeBnTuN5sJwk+kBDruJDZmJ67h_AF!?9L4whLiK2fH%H zB&Dmk3^3%XymE~$uHD+xKeInU(GJnl`Er#nyNS4iVfpr^nPB5mN|d5e(SBq-hoAE> z63+H9%(7F-6`4EbPlw5pnk`-35j18FkOJe%GhjNx2=8Fbhhz8T-^U7#Ep7`@UU#f` z{qjfMkG_n(>A;a4%?E)I#X09M7a@ddKl<{oxgU)TAZbpa{m8uVCaGRV9A9Du_oE^$ z#OrEa+ue_fR4un3X??tV;M?^=RJ#QI)N7uVZbICg@O1dksgM-2ppai?i{?zulw|>O z2T)q2RBP=Ml!A~c0erF$n?gk~C~l63{zq_;Zm^y#Z&6aZ;Pd&XnPpgjIJwX_YzC zKO6dstMtLwAE{&f1Xews7tK}l3j<#BGN`QLu=z4rI?r9!6EON&uua^P6m((o8tEA} z=002#WP8QGQTy}|`jK%AZ}VaHi=ZA1XFDYh8^|p{4j+W;AASCwu^0A)V=uzO3AcpY z{sm*NF+|}bf=_DZASKKB-}~$*>JQ^z?F#$8dH*7^EY(4M7e#9WII(fC5+_Otd1EQ> zA-J4j&oD7dymsk?xcpTJi7$bQKMSJkFrVS$%Hd4F)O%J11R+T2OM`~mHx2LL8^B(C z{Em^qR#nBCw5y+>rmj2$o994N*Z^-sav;;rd!#~M;Y{Bu-c*`ZMoi@HYH(vA(Sm^e zl{>C42d`L^1rv(Fi@9Qk&k~0u)}du>06foLF4B0-FV&f9kFwy!NV1|LuEBfRwh{gC zs=U`D$sp;3d9)|Uqrb+{f0F1W9Sk4uz&nZpx35Quw*LUnW= z@ul1Yk96BTq$3M+m^ABHMT52Z6P6#uo5xJAO)g|axm4Xzv-%Amqo)2bD zAj&BA9vXWSw`BddJ)7W8!##g_AMq&|NUn&+UlFg4G0=#oadd+ zb+>bU%DFy4*Q=Zdw>sCzaA5wvUIC8_=LG++g2fxH<&M;wHdWE@50mVs@Y#UWF-iCagZ9Zj!yP>RX#2`QIJYTNAs=^>%&Y}_b@`q z$}aK92+q)gdO<4_>!7|s9uCtqqSjJ;@VF=6FM~ST&ESCY2C}JCBNl%OX9g(l6VhBz zUKQmnO);BkT4>rnk6=*;yf1hq-p%9u(O55)M3>A)4BQ>c$fwG9@KszOj^&7(OV9he z#D_%6AfmOnXV5K$*4@WN!s9*UWCed?iV2J;-t>A-CHx|A4Yi279km2?3n*O(O(2}; z7f?FyYC0Q{Zj^M6iV>mDy;hL2MY+nTeKR0?LJ)ip@z<3EvTnQ18~RZCWCE5X>b+Z{j`W31z5d{6bLdkr zB*zf|$Tno)v^JOx-pq0gcgb9{B_#-!!FJ5zJ?PpoW~iJ)lBNx{diJBKn)j}zwc!Q{ z34&X6n5j8PTfDa$zTouv$Si#4X5raLzS$OQ?k7QV5f966Ye}b~-%M$bnLDlVQe*jE zP+wj9&NZAW20?!?^QqDEqt7C>6{QYZnF!Nk7vK>r3z-5wUfb6XEi3%c1-e+j2sKR9 z4^f(Dvb=eV34;GX%U5s((C@_>r}oT3=NrtfbRziP*UKo=+yT#dOc@+~1SlBs$_=*!;KKo<%k_vObO9NdrW)u$ z3PBK3$h$)n5@*>J5i|rg>c#=#VC~Q_(M|n#SFmt5A5Y-p4mg8wydAeN2s*iM{w!&O zJlnmws@$&k*kZS5_i!*km-96LB$Y%r=gFD4769ng5)IBVr2Cc z%KNyiX1lZdlTv87(9xozh#f=w5I_%=b?hTdF~8{Sfte9!D0p;D;c+2d{K`J$?mK(> zq4_|RMgTv{&V-%30@4&Rvk@9!MtKF z*A)cOwf4&}AH(e@XhwW6sSJ~V61~P;)o&#ggUpboL3~D|2_Xt70oj%<*Z}EVkON^| zx0dSid+M$MoG){qf z9)!Xx)d`I_Z$_JgTd+ZaFd>5fwTaa0+0^UEFp;L*!ys8mk#j9#_NRqFQz`<=q3oqd zZWGK`1n8BM-I>Am!OoL(#z3%*(giARmM;NbKYW!G!(*BhVNToR8SiLb7%#)%wnns?$C}@R?MsLr^l%4ypKO0tX=R1w&(?rTc;Kznp-I5*TUB?FH0;OJ&&Vq zZI>GosfEDtRUj>DyJjFL%5fe5fk3W6z>*sfZh=+`nAF~&!-NmmzA;I4boPY~N?|(; zaTown?O=C%2Nc#)1{;&R`wF{)dUjiva`;fUTwbT~4c^pp{AY2976(C(D%2@9fFO~f7A2d1 z)g$6t2xXcL>!oOhLxH;Vo-m|Klw*C{YM)@Qe1pPQ&p zjGq`ROA=H^fIZId;08;J{^iwQpu_irWOV`7Wxb*>#04n~q?2vKgP9B+2~%&0FA}zv zhG6OcrXX2WpwTLt4B>_7%9{T4-Wn|nZa~JOy7nB%Oh<0T=reVr=Pz~UR&?ba%J5!5 z3yS&Q4${TjBj7>sU#LmuB5~@Bn3GP6U`^p^`Ve)-t({pqS@)#a=5t~v5JUoJ7hxo7 z?}YRU$aZ%Q(f2U|5!M3t-6ce%1CKN$aO<;a2pBv^B9fUo)fewOva}V(qsTeJp7Cb%Q|Qx|%6LV$(9=PhL*O`Zmr8=v z-~IsJt5k2!BPNI zeC3w5D?$O`uY-k)l$e*{tdNVG@T6+HJmdiZnO!}7O8ZXQzjzTDoQWrEH7|fndiNw6 zOWGUr4x8a#WHg1oms_+Lr{^pB&%xsA+KFo+m*8~rCHTeQ2p7V|@Eqe1Vz5wX51+b( zpRtGpAjWqoPWbCq99JUR^N3&3f7%Gzw08CrNieHO-rtuw+r%V>UV?2h;sz$1c`+?+ z6dC~YV!CA^dqVt)qrpm3bzIrpOjAdYlpMjn95;_vE#V&jn$iYPpTwbt+U+f4=XSJ= zoqxhv1+?Mwrf-UNdXxJwd}Wvpk^IFS&{;qCp-aP*f)t345FWI-a#f(a;I`4A>H_%# z%>;A9+BzEP6VC{`(=q%~BWx=+7!Xt?`xPxAtkso_uanjBJ8D7nN1sIs=oL>Om*73M zsnHi|!@4If;`HWywWBXzlA%+w?jVT}$e2;$0l*O?9k~m-I|KFThYsz*i_ZtudemxD z0Aom)E3>0{vr1ivrpKL$(|MoPc^dmHu&$lBHvYxhfwRW;LL5E-H92!!DrD_jfLiXH7M41>XB*gzvtJ_y4*Kq!Zkc7sq$)`N? z3Usf&5cpr*A(Ho2->?O`r*ub#^oBzEc73p!-VKew5X(~;Zr#fJ4l~Adh9r@GRM5RweGKGn)V-#%Rk(Ri@dF zQkt=?9$r3ux|_%-Rw}q>*@LjP3!ItJC7m>wD7qHbP>K>S)gYHv18iDwwyJWPF{0n1 zOXp4tKZz=iJzUMk%G99;`|hl8KO$Xh-!b19UU$I?@)SR&3;SJj zlG}o-SOCMmzLgMao6h>dmXeAfYVOGNb)M{PA3W8FOp1t2(N9*1E*L&Sxc!N(mG7{X z$7MTWrBMiP4tMuL8tH|#wtFZ88+g~y;r@f5x7QLoIfIf~za|A{cbU&r(ZdkJ^}a@|tK^tCb& zexyrqxA3 zriX&t8Tngxyi*Mnhplq_x$OA!IcU0bd)`@~?$dPL3)v3VLM(wHBpe--0B{mjmH}fD^;921qTy-Hdq9Pl(I9E z325}0LHu)mh@s=!Im+-hGSDPRVimz?cOO}_2y_U5gs4)Fh034LhC{SPkOQ9P@GNb+ zJY@SyCCq!Qz^nCAi}5&Ct3cYf&$vtwJ79Z|U1+;Z^I#C^BEAnsqi!3)HTT-|pfQGO zxoxdcT2tve#hE&<;c-aauA)-NK@@$)8)Kbqb-lD2`|NaH!%qgYL7D{9XC>=mW7c{R z#Rjgn?QbYBYFV3#QPeA}2`O2)d6zk_2v#UJl)CV2W{88_QOG!A%vqtTQ?WQsPEqth zu&`wy*SZ~oQL6RQKo+6Xl(h{bm)-Cwfd`uzD}qDtG9TEXc|DL1%^xuThJ<^BR7EL2 z7aD@j2C>f+YjDi9b;un2iuZoYEkUF}d#>C_0KnS!m`xEKl)4xNSTUN3C^XGB8ea+x6=8SmrW!)hC+DS~$?EMByB z*@8#?*#fGi!A!>p`~dg#wx7VM01#^icAFU%R_f%ksRB zQgldVgg2$Shs5Y;NvwI*)xT%h?iXO51!wUEmV>(^NFFVKp0{axY8{l^7Cmvy5iiyl zG(8S)%zXEUXe?NURQKSB#ZV4G2q~;!P=uYl5_3=6J22K|=Um%xMey6aPu;6+C^#rcrMY>gngo6^Pz0TLSSM zOO#pdz4JDQXpOUMc_kcDDO-6HCIzw-5Q4zw|AKE?AT%Li<7wHvmT z>vkfxD=2-uVd3Qjk>c-otb4(a7JMP(YHxr4sgZ$#1r7~EJfMiSLj_;&fSlfWGVhh% zzRY|^vx05F;YV_4Y#CIL{$yw<>7FJaN==sjCLW^GwA!UQ`+Mli?r9(F zz&>Rg>%~{1sp2Xg-tb`fgq!rx5UR4NMPNkzIj0+BTwox~NQ;XsX}w+94ay>5b1=I$ z#si-BR`?2nkYRV4c;n|zk1-2CZ}a%SD7q_H2D}DewPO;(DF`&8B|&#Vf~jV&;Cd>u zXjMIwa6f?%sgcVHq-rRN^ZIvsvan|{a>$ld7*8Ay%+(ce;o@SYZi|1ZJDELk;o{G( zlX$8t8XCjRFo|qL!kbjM1t_@z_v=^Hqq9A_)|rr-s4D=VyUW47PJb+f!1ldZ0s?fo z=5`CTdS7r2IN7Tc&G7qW+5kCo7N^YMD1C~NRobp~h}PM8>Hy9RAIHINCRRFXtO1?^ z9mU_H?r0+HF)fQl>gsAegYh@BofkiJu2IfW*i)_lH?|p*h`c=cu*sOpE +#include "patterns/InitVMImage.h" +#include + +class P_Inference : public P_InitVM_Image +{ +public: + type::TypeSystem* m_type_system; + + P_Inference() : P_InitVM_Image(), m_type_system(0) {} + virtual ~P_Inference() {} + + virtual void SetUp() + { + P_InitVM_Image::SetUp(); + m_type_system = new type::TypeSystem(*m_vm); + } + virtual void TearDown() { + P_InitVM_Image::TearDown(); + delete m_type_system; + } + + type::InferContext* inferMessage( + TClass* const objectClass, + const std::string& methodName, + const type::Type& args, + type::TContextStack* parent, + bool sendToSuper = false + ) { + EXPECT_NE(static_cast(0), objectClass) << "Class is null"; + TMethod* const method = objectClass->methods->find(methodName.c_str()); + EXPECT_NE(static_cast(0), method) << "Method is null"; + + TSymbol* const selector = method->name; + return m_type_system->inferMessage(selector, args, parent, sendToSuper); + } + + type::InferContext* inferMessage( + const std::string& className, + const std::string& methodName, + const type::Type& args, + type::TContextStack* parent, + bool sendToSuper = false + ) { + TClass* const objectClass = m_image->getGlobal(className.c_str()); + return this->inferMessage(objectClass, methodName, args, parent, sendToSuper); + } + +}; + +INSTANTIATE_TEST_CASE_P(_, P_Inference, ::testing::Values(std::string("Inference")) ); + +template +std::vector convert_array_to_vector(const T (&source_array)[N]) { + return std::vector(source_array, source_array+N); +} + +TEST_P(P_Inference, new) +{ + std::string types_array[] = {"Array", "List", "True", "False", "Dictionary"}; + std::vector types = convert_array_to_vector<>(types_array); + + for(std::vector::const_iterator it = types.begin(); it != types.end(); ++it) { + const std::string& type = *it; + SCOPED_TRACE(type); + TClass* const klass = m_image->getGlobal(type.c_str()); + ASSERT_TRUE(klass != 0) << "could not find class for " << type; + + TClass* const metaClass = klass->getClass(); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(klass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage(metaClass, "new", args, 0, true); + ASSERT_EQ( type::Type(klass), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__isNil) +{ + { + SCOPED_TRACE("nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.nilObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isNil", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("not nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isNil", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__notNil) +{ + { + SCOPED_TRACE("nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.nilObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "notNil", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("not nil"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Object", "notNil", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__class) +{ + TClass* classes_array[] = {globals.smallIntClass, globals.stringClass, globals.arrayClass, globals.blockClass}; + std::vector classes = convert_array_to_vector<>(classes_array); + + for(std::vector::const_iterator it = classes.begin(); it != classes.end(); ++it) { + TClass* klass = *it; + SCOPED_TRACE(klass->name->toString()); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(klass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "class", args, 0); + ASSERT_EQ( type::Type(klass, type::Type::tkLiteral), inferContext->getReturnType() ) ; + } +} + +TEST_P(P_Inference, Object__isMemberOf) +{ + { + SCOPED_TRACE("42 isMemberOf: SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isMemberOf:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("42 isMemberOf: (SmallInt)"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isMemberOf:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Object__isKindOf) +{ + { + SCOPED_TRACE("42 isKindOf: SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass, type::Type::tkLiteral)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isKindOf:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("42 isKindOf: Number"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.smallIntClass)); + + type::InferContext* const inferContext = this->inferMessage("Object", "isKindOf:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, Object__respondsTo) +{ + { + SCOPED_TRACE("42 respondsTo: #<"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + args.pushSubType(type::Type(globals.binaryMessages[0])); + + type::InferContext* const inferContext = this->inferMessage("Object", "respondsTo:", args, 0); + ASSERT_EQ( type::Type(type::Type::tkPolytype), inferContext->getReturnType() ); // FIXME + } +} + +TEST_P(P_Inference, Collection__includes) +{ + { + SCOPED_TRACE("Array new includes: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, OrderedArray) +{ + { + SCOPED_TRACE("OrderedArray new location: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("OrderedArray"))); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("OrderedArray", "location:", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, True) +{ + { + SCOPED_TRACE("True>>not"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("True", "not", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("True>>and:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("True", "and:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("True>>or:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("True", "or:", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, False) +{ + { + SCOPED_TRACE("False>>not"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + + type::InferContext* const inferContext = this->inferMessage("False", "not", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False>>and:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("False", "and:", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False>>or:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("False", "or:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} + +TEST_P(P_Inference, Boolean) +{ + { + SCOPED_TRACE("not"); + { + SCOPED_TRACE("False"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "not", args, 0, true); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("False"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "not", args, 0, true); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("and:"); + { + SCOPED_TRACE("False + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "and:", args, 0, true); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("True + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "and:", args, 0, true); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + } + { + SCOPED_TRACE("or:"); + { + SCOPED_TRACE("False + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.falseObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "or:", args, 0, true); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("True + block"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.trueObject)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Boolean", "or:", args, 0, true); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + } +} + +TEST_P(P_Inference, SmallInt) +{ + { + SCOPED_TRACE("SmallInt>>asSmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("SmallInt", "asSmallInt", args, 0); + ASSERT_EQ( type::Type(TInteger(42)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("SmallInt>>+ SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(40))); + args.pushSubType(type::Type(TInteger(2))); + + type::InferContext* const inferContext = this->inferMessage("SmallInt", "+", args, 0); + ASSERT_EQ( type::Type(TInteger(42)), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Number) +{ + { + SCOPED_TRACE("Number::new"); + type::Type args(globals.arrayClass, type::Type::tkArray); + TClass* const metaNumberClass = m_image->getGlobal("Number")->getClass(); + args.pushSubType(type::Type(metaNumberClass)); + + type::InferContext* const inferContext = this->inferMessage(metaNumberClass, "new", args, 0); + ASSERT_EQ( type::Type(TInteger(0)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Number>>factorial"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(4))); + + type::InferContext* const inferContext = this->inferMessage("Number", "factorial", args, 0); + ASSERT_EQ( type::Type(TInteger(24)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Number>>negative"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negative", args, 0); + ASSERT_EQ( type::Type(globals.trueObject), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negative", args, 0); + ASSERT_EQ( type::Type(globals.falseObject), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>negated"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negated", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "negated", args, 0); + ASSERT_EQ( type::Type(TInteger(-1)), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>absolute"); + { + SCOPED_TRACE("-SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(-1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "absolute", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("+SmallInt"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + + type::InferContext* const inferContext = this->inferMessage("Number", "absolute", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } + } + { + SCOPED_TRACE("Number>>to:do:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(TInteger(1))); + args.pushSubType(type::Type(TInteger(100))); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Number", "to:do:", args, 0); + ASSERT_EQ( type::Type(TInteger(1)), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, String) +{ + { + SCOPED_TRACE("String>>words"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.stringClass)); + + type::InferContext* const inferContext = this->inferMessage("String", "words", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("List")), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Array) +{ + { + SCOPED_TRACE("Array>>sort:"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + args.pushSubType(type::Type(globals.blockClass)); + + type::InferContext* const inferContext = this->inferMessage("Array", "sort:", args, 0); + ASSERT_EQ( type::Type(globals.arrayClass), inferContext->getReturnType() ); + } + { + SCOPED_TRACE("Array>>size"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(globals.arrayClass)); + + type::InferContext* const inferContext = this->inferMessage("Array", "size", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType() ); + } +} + +TEST_P(P_Inference, Char) +{ + TClass* const charClass = m_image->getGlobal("Char"); + ASSERT_TRUE(charClass != 0) << "could not find class for Char"; + TClass* const metaCharClass = charClass->getClass(); + + { + SCOPED_TRACE("Char::basicNew:"); + + type::Type argsBasicNew(globals.arrayClass, type::Type::tkArray); + argsBasicNew.pushSubType(type::Type(charClass, type::Type::tkLiteral)); + argsBasicNew.pushSubType(type::Type(TInteger(33))); + + type::InferContext* const inferBasicNewContext = this->inferMessage(metaCharClass, "basicNew:", argsBasicNew, 0); + ASSERT_EQ( type::Type(charClass), inferBasicNewContext->getReturnType() ); + + /*{ + SCOPED_TRACE("Char>>value"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(inferBasicNewContext->getReturnType())); + + type::InferContext* const inferContext = this->inferMessage(charClass, "value", args, 0); + ASSERT_EQ( type::Type(globals.smallIntClass), inferContext->getReturnType() ); + }*/ + } + { + SCOPED_TRACE("Char::new:"); + + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(charClass, type::Type::tkLiteral)); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferBasicNewContext = this->inferMessage(metaCharClass, "new:", args, 0); + ASSERT_EQ( type::Type(type::Type::tkPolytype), inferBasicNewContext->getReturnType() ); + } +} + +TEST_P(P_Inference, DISABLED_Includes) +{ + { + SCOPED_TRACE("Dictionary new includes: #asd"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("Dictionary"))); + args.pushSubType(type::Type(m_image->getGlobal("Symbol"))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } + { + SCOPED_TRACE("OrderedArray new includes: 42"); + type::Type args(globals.arrayClass, type::Type::tkArray); + args.pushSubType(type::Type(m_image->getGlobal("OrderedArray"))); + args.pushSubType(type::Type(TInteger(42))); + + type::InferContext* const inferContext = this->inferMessage("Collection", "includes:", args, 0); + ASSERT_EQ( type::Type(m_image->getGlobal("Boolean")), inferContext->getReturnType().fold() ); + } +} diff --git a/tests/patterns/InitVMImage.h b/tests/patterns/InitVMImage.h index 7206014..bef2b7f 100644 --- a/tests/patterns/InitVMImage.h +++ b/tests/patterns/InitVMImage.h @@ -2,13 +2,16 @@ #define LLST_PATTERN_INIT_VM_IMAGE_INCLUDED #include -#include "../helpers/VMImage.h" +#include class P_InitVM_Image : public ::testing::TestWithParam { protected: - H_VMImage* m_image; + std::auto_ptr m_memoryManager; public: + std::auto_ptr m_image; + std::auto_ptr m_vm; + P_InitVM_Image() : m_memoryManager(), m_image(), m_vm() {} virtual ~P_InitVM_Image() {} virtual void SetUp() @@ -19,10 +22,21 @@ class P_InitVM_Image : public ::testing::TestWithParaminitializeHeap(1024*1024, 1024*1024); + m_image.reset(new Image(m_memoryManager.get())); + m_image->loadImage(TESTS_DIR "./data/" + image_name + ".image"); + m_vm.reset(new SmalltalkVM(m_image.get(), m_memoryManager.get())); } virtual void TearDown() { - delete m_image; + m_vm.reset(); + m_image.reset(); + m_memoryManager.reset(); } };