From 670df5063bd02753599d7d4ca53a2501ee4f770a Mon Sep 17 00:00:00 2001 From: "titzer@chromium.org" Date: Mon, 11 Aug 2014 09:40:02 +0000 Subject: [PATCH] Implement representation selection as part of SimplifiedLowering. Representation selection also requires inserting explicit representation change nodes to be inserted in the graph. Such nodes are pure, but also need to be lowered to machine operators. They need to be scheduled first, to determine the control input for any branches inside. This CL requires extensive testing. More tests to follow. R=rossberg@chromium.org BUG= Review URL: https://codereview.chromium.org/425003004 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@23026 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/compiler/lowering-builder.cc | 8 +- src/compiler/node-properties.h | 1 - src/compiler/pipeline.cc | 4 +- src/compiler/representation-change.h | 47 +- src/compiler/simplified-lowering.cc | 775 +++++++++- src/compiler/simplified-lowering.h | 31 +- test/cctest/compiler/call-tester.h | 1 + test/cctest/compiler/graph-builder-tester.h | 4 +- test/cctest/compiler/test-changes-lowering.cc | 6 +- .../compiler/test-simplified-lowering.cc | 1261 ++++++++++++++--- 10 files changed, 1901 insertions(+), 237 deletions(-) diff --git a/src/compiler/lowering-builder.cc b/src/compiler/lowering-builder.cc index f3644cfef2..1246f54f14 100644 --- a/src/compiler/lowering-builder.cc +++ b/src/compiler/lowering-builder.cc @@ -16,8 +16,12 @@ class LoweringBuilder::NodeVisitor : public NullNodeVisitor { explicit NodeVisitor(LoweringBuilder* lowering) : lowering_(lowering) {} GenericGraphVisit::Control Post(Node* node) { - SourcePositionTable::Scope pos(lowering_->source_positions_, node); - lowering_->Lower(node); + if (lowering_->source_positions_ != NULL) { + SourcePositionTable::Scope pos(lowering_->source_positions_, node); + lowering_->Lower(node); + } else { + lowering_->Lower(node); + } return GenericGraphVisit::CONTINUE; } diff --git a/src/compiler/node-properties.h b/src/compiler/node-properties.h index 001bd0286f..eaa4d66ada 100644 --- a/src/compiler/node-properties.h +++ b/src/compiler/node-properties.h @@ -40,7 +40,6 @@ class NodeProperties { static inline int GetContextIndex(Node* node); - private: static inline int FirstValueIndex(Node* node); static inline int FirstEffectIndex(Node* node); static inline int FirstControlIndex(Node* node); diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc index ef7aade610..190661f2bb 100644 --- a/src/compiler/pipeline.cc +++ b/src/compiler/pipeline.cc @@ -231,7 +231,7 @@ Schedule* Pipeline::ComputeSchedule(Graph* graph) { Handle Pipeline::GenerateCodeForMachineGraph(Linkage* linkage, Graph* graph, Schedule* schedule) { - CHECK(SupportedTarget()); + CHECK(SupportedBackend()); if (schedule == NULL) { VerifyAndPrintGraph(graph, "Machine"); schedule = ComputeSchedule(graph); @@ -257,7 +257,7 @@ Handle Pipeline::GenerateCode(Linkage* linkage, Graph* graph, DCHECK_NOT_NULL(graph); DCHECK_NOT_NULL(linkage); DCHECK_NOT_NULL(schedule); - DCHECK(SupportedTarget()); + CHECK(SupportedBackend()); InstructionSequence sequence(linkage, graph, schedule); diff --git a/src/compiler/representation-change.h b/src/compiler/representation-change.h index 04d94642c0..255073003d 100644 --- a/src/compiler/representation-change.h +++ b/src/compiler/representation-change.h @@ -36,14 +36,28 @@ enum RepType { tAny = 1 << 11 }; +#define REP_TYPE_STRLEN 24 + typedef uint16_t RepTypeUnion; + +inline void RenderRepTypeUnion(char* buf, RepTypeUnion info) { + base::OS::SNPrintF(buf, REP_TYPE_STRLEN, "{%s%s%s%s%s %s%s%s%s%s%s%s}", + (info & rBit) ? "k" : " ", (info & rWord32) ? "w" : " ", + (info & rWord64) ? "q" : " ", + (info & rFloat64) ? "f" : " ", + (info & rTagged) ? "t" : " ", (info & tBool) ? "Z" : " ", + (info & tInt32) ? "I" : " ", (info & tUint32) ? "U" : " ", + (info & tInt64) ? "L" : " ", (info & tUint64) ? "J" : " ", + (info & tNumber) ? "N" : " ", (info & tAny) ? "*" : " "); +} + + const RepTypeUnion rMask = rBit | rWord32 | rWord64 | rFloat64 | rTagged; const RepTypeUnion tMask = tBool | tInt32 | tUint32 | tInt64 | tUint64 | tNumber | tAny; const RepType rPtr = kPointerSize == 4 ? rWord32 : rWord64; - // Contains logic related to changing the representation of values for constants // and other nodes, as well as lowering Simplified->Machine operators. // Eagerly folds any representation changes for constants. @@ -344,10 +358,24 @@ class RepresentationChanger { return static_cast(tElement | rElement); } - RepType TypeForBasePointer(Node* node) { - Type* upper = NodeProperties::GetBounds(node).upper; - if (upper->Is(Type::UntaggedPtr())) return rPtr; - return static_cast(tAny | rTagged); + RepType TypeForBasePointer(const FieldAccess& access) { + if (access.tag() != 0) return static_cast(tAny | rTagged); + return kPointerSize == 8 ? rWord64 : rWord32; + } + + RepType TypeForBasePointer(const ElementAccess& access) { + if (access.tag() != 0) return static_cast(tAny | rTagged); + return kPointerSize == 8 ? rWord64 : rWord32; + } + + RepType TypeFromUpperBound(Type* type) { + if (type->Is(Type::None())) + return tAny; // TODO(titzer): should be an error + if (type->Is(Type::Signed32())) return tInt32; + if (type->Is(Type::Unsigned32())) return tUint32; + if (type->Is(Type::Number())) return tNumber; + if (type->Is(Type::Boolean())) return tBool; + return tAny; } private: @@ -364,7 +392,14 @@ class RepresentationChanger { Node* TypeError(Node* node, RepTypeUnion output_type, RepTypeUnion use) { type_error_ = true; if (!testing_type_errors_) { - UNREACHABLE(); // TODO(titzer): report nicer type error + char buf1[REP_TYPE_STRLEN]; + char buf2[REP_TYPE_STRLEN]; + RenderRepTypeUnion(buf1, output_type); + RenderRepTypeUnion(buf2, use); + V8_Fatal(__FILE__, __LINE__, + "RepresentationChangerError: node #%d:%s of rep" + "%s cannot be changed to rep%s", + node->id(), node->op()->mnemonic(), buf1, buf2); } return node; } diff --git a/src/compiler/simplified-lowering.cc b/src/compiler/simplified-lowering.cc index de5fd3efde..3ef9d30fcd 100644 --- a/src/compiler/simplified-lowering.cc +++ b/src/compiler/simplified-lowering.cc @@ -4,14 +4,706 @@ #include "src/compiler/simplified-lowering.h" +#include +#include + +#include "src/compiler/common-operator.h" #include "src/compiler/graph-inl.h" #include "src/compiler/node-properties-inl.h" +#include "src/compiler/representation-change.h" +#include "src/compiler/simplified-lowering.h" +#include "src/compiler/simplified-operator.h" #include "src/objects.h" namespace v8 { namespace internal { namespace compiler { +// Macro for outputting trace information from representation inference. +#define TRACE(x) \ + if (FLAG_trace_representation) PrintF x + +// Representation selection and lowering of {Simplified} operators to machine +// operators are interwined. We use a fixpoint calculation to compute both the +// output representation and the best possible lowering for {Simplified} nodes. +// Representation change insertion ensures that all values are in the correct +// machine representation after this phase, as dictated by the machine +// operators themselves. +enum Phase { + // 1.) PROPAGATE: Traverse the graph from the end, pushing usage information + // backwards from uses to definitions, around cycles in phis, according + // to local rules for each operator. + // During this phase, the usage information for a node determines the best + // possible lowering for each operator so far, and that in turn determines + // the output representation. + // Therefore, to be correct, this phase must iterate to a fixpoint before + // the next phase can begin. + PROPAGATE, + + // 2.) LOWER: perform lowering for all {Simplified} nodes by replacing some + // operators for some nodes, expanding some nodes to multiple nodes, or + // removing some (redundant) nodes. + // During this phase, use the {RepresentationChanger} to insert + // representation changes between uses that demand a particular + // representation and nodes that produce a different representation. + LOWER +}; + + +class RepresentationSelector { + public: + // Information for each node tracked during the fixpoint. + struct NodeInfo { + RepTypeUnion use : 14; // Union of all usages for the node. + bool queued : 1; // Bookkeeping for the traversal. + bool visited : 1; // Bookkeeping for the traversal. + RepTypeUnion output : 14; // Output type of the node. + }; + + RepresentationSelector(JSGraph* jsgraph, Zone* zone, + RepresentationChanger* changer) + : jsgraph_(jsgraph), + count_(jsgraph->graph()->NodeCount()), + info_(zone->NewArray(count_)), + nodes_(NodeVector::allocator_type(zone)), + replacements_(NodeVector::allocator_type(zone)), + contains_js_nodes_(false), + phase_(PROPAGATE), + changer_(changer), + queue_(std::deque( + NodePtrZoneAllocator(zone))) { + memset(info_, 0, sizeof(NodeInfo) * count_); + } + + void Run(SimplifiedLowering* lowering) { + // Run propagation phase to a fixpoint. + TRACE(("--{Propagation phase}--\n")); + phase_ = PROPAGATE; + Enqueue(jsgraph_->graph()->end()); + // Process nodes from the queue until it is empty. + while (!queue_.empty()) { + Node* node = queue_.front(); + NodeInfo* info = GetInfo(node); + queue_.pop(); + info->queued = false; + TRACE((" visit #%d: %s\n", node->id(), node->op()->mnemonic())); + VisitNode(node, info->use, NULL); + TRACE((" ==> output ")); + PrintInfo(info->output); + TRACE(("\n")); + } + + // Run lowering and change insertion phase. + TRACE(("--{Simplified lowering phase}--\n")); + phase_ = LOWER; + // Process nodes from the collected {nodes_} vector. + for (NodeVector::iterator i = nodes_.begin(); i != nodes_.end(); ++i) { + Node* node = *i; + TRACE((" visit #%d: %s\n", node->id(), node->op()->mnemonic())); + // Reuse {VisitNode()} so the representation rules are in one place. + VisitNode(node, GetUseInfo(node), lowering); + } + + // Perform the final replacements. + for (NodeVector::iterator i = replacements_.begin(); + i != replacements_.end(); ++i) { + Node* node = *i; + Node* replacement = *(++i); + node->ReplaceUses(replacement); + } + } + + // Enqueue {node} if the {use} contains new information for that node. + // Add {node} to {nodes_} if this is the first time it's been visited. + void Enqueue(Node* node, RepTypeUnion use = 0) { + if (phase_ != PROPAGATE) return; + NodeInfo* info = GetInfo(node); + if (!info->visited) { + // First visit of this node. + info->visited = true; + info->queued = true; + nodes_.push_back(node); + queue_.push(node); + TRACE((" initial: ")); + info->use |= use; + PrintUseInfo(node); + return; + } + TRACE((" queue?: ")); + PrintUseInfo(node); + if ((info->use & use) != use) { + // New usage information for the node is available. + if (!info->queued) { + queue_.push(node); + info->queued = true; + TRACE((" added: ")); + } else { + TRACE((" inqueue: ")); + } + info->use |= use; + PrintUseInfo(node); + } + } + + bool lower() { return phase_ == LOWER; } + + void Enqueue(Node* node, RepType use) { + Enqueue(node, static_cast(use)); + } + + void SetOutput(Node* node, RepTypeUnion output) { + // Every node should have at most one output representation. Note that + // phis can have 0, if they have not been used in a representation-inducing + // instruction. + DCHECK((output & rMask) == 0 || IsPowerOf2(output & rMask)); + GetInfo(node)->output = output; + } + + bool BothInputsAre(Node* node, Type* type) { + DCHECK_EQ(2, node->InputCount()); + return NodeProperties::GetBounds(node->InputAt(0)).upper->Is(type) && + NodeProperties::GetBounds(node->InputAt(1)).upper->Is(type); + } + + void ProcessInput(Node* node, int index, RepTypeUnion use) { + Node* input = node->InputAt(index); + if (phase_ == PROPAGATE) { + // In the propagate phase, propagate the usage information backward. + Enqueue(input, use); + } else { + // In the change phase, insert a change before the use if necessary. + if ((use & rMask) == 0) return; // No input requirement on the use. + RepTypeUnion output = GetInfo(input)->output; + if ((output & rMask & use) == 0) { + // Output representation doesn't match usage. + TRACE((" change: #%d:%s(@%d #%d:%s) ", node->id(), + node->op()->mnemonic(), index, input->id(), + input->op()->mnemonic())); + TRACE((" from ")); + PrintInfo(output); + TRACE((" to ")); + PrintInfo(use); + TRACE(("\n")); + Node* n = changer_->GetRepresentationFor(input, output, use); + node->ReplaceInput(index, n); + } + } + } + + static const RepTypeUnion kFloat64 = rFloat64 | tNumber; + static const RepTypeUnion kInt32 = rWord32 | tInt32; + static const RepTypeUnion kUint32 = rWord32 | tUint32; + static const RepTypeUnion kInt64 = rWord64 | tInt64; + static const RepTypeUnion kUint64 = rWord64 | tUint64; + static const RepTypeUnion kAnyTagged = rTagged | tAny; + + // The default, most general visitation case. For {node}, process all value, + // context, effect, and control inputs, assuming that value inputs should have + // {rTagged} representation and can observe all output values {tAny}. + void VisitInputs(Node* node) { + InputIter i = node->inputs().begin(); + for (int j = OperatorProperties::GetValueInputCount(node->op()); j > 0; + ++i, j--) { + ProcessInput(node, i.index(), kAnyTagged); // Value inputs + } + for (int j = OperatorProperties::GetContextInputCount(node->op()); j > 0; + ++i, j--) { + ProcessInput(node, i.index(), kAnyTagged); // Context inputs + } + for (int j = OperatorProperties::GetEffectInputCount(node->op()); j > 0; + ++i, j--) { + Enqueue(*i); // Effect inputs: just visit + } + for (int j = OperatorProperties::GetControlInputCount(node->op()); j > 0; + ++i, j--) { + Enqueue(*i); // Control inputs: just visit + } + SetOutput(node, kAnyTagged); + } + + // Helper for binops of the I x I -> O variety. + void VisitBinop(Node* node, RepTypeUnion input_use, RepTypeUnion output) { + DCHECK_EQ(2, node->InputCount()); + ProcessInput(node, 0, input_use); + ProcessInput(node, 1, input_use); + SetOutput(node, output); + } + + // Helper for unops of the I -> O variety. + void VisitUnop(Node* node, RepTypeUnion input_use, RepTypeUnion output) { + DCHECK_EQ(1, node->InputCount()); + ProcessInput(node, 0, input_use); + SetOutput(node, output); + } + + // Helper for leaf nodes. + void VisitLeaf(Node* node, RepTypeUnion output) { + DCHECK_EQ(0, node->InputCount()); + SetOutput(node, output); + } + + // Helpers for specific types of binops. + void VisitFloat64Binop(Node* node) { VisitBinop(node, kFloat64, kFloat64); } + void VisitInt32Binop(Node* node) { VisitBinop(node, kInt32, kInt32); } + void VisitUint32Binop(Node* node) { VisitBinop(node, kUint32, kUint32); } + void VisitInt64Binop(Node* node) { VisitBinop(node, kInt64, kInt64); } + void VisitUint64Binop(Node* node) { VisitBinop(node, kUint64, kUint64); } + void VisitFloat64Cmp(Node* node) { VisitBinop(node, kFloat64, rBit); } + void VisitInt32Cmp(Node* node) { VisitBinop(node, kInt32, rBit); } + void VisitUint32Cmp(Node* node) { VisitBinop(node, kUint32, rBit); } + void VisitInt64Cmp(Node* node) { VisitBinop(node, kInt64, rBit); } + void VisitUint64Cmp(Node* node) { VisitBinop(node, kUint64, rBit); } + + // Helper for handling phis. + void VisitPhi(Node* node, RepTypeUnion use) { + // First, propagate the usage information to inputs of the phi. + int values = OperatorProperties::GetValueInputCount(node->op()); + Node::Inputs inputs = node->inputs(); + for (Node::Inputs::iterator iter(inputs.begin()); iter != inputs.end(); + ++iter, --values) { + // Propagate {use} of the phi to value inputs, and 0 to control. + // TODO(titzer): it'd be nice to have distinguished edge kinds here. + ProcessInput(node, iter.index(), values > 0 ? use : 0); + } + // Phis adapt to whatever output representation their uses demand, + // pushing representation changes to their inputs. + RepTypeUnion use_rep = GetUseInfo(node) & rMask; + RepTypeUnion use_type = GetUseInfo(node) & tMask; + RepTypeUnion rep = 0; + if (use_rep & rTagged) { + rep = rTagged; // Tagged overrides everything. + } else if (use_rep & rFloat64) { + rep = rFloat64; + } else if (use_rep & rWord64) { + rep = rWord64; + } else if (use_rep & rWord32) { + rep = rWord32; + } else if (use_rep & rBit) { + rep = rBit; + } else { + // There was no representation associated with any of the uses. + // TODO(titzer): Select the best rep using phi's type, not the usage type? + if (use_type & tAny) { + rep = rTagged; + } else if (use_type & tNumber) { + rep = rFloat64; + } else if (use_type & tInt64 || use_type & tUint64) { + rep = rWord64; + } else if (use_type & tInt32 || use_type & tUint32) { + rep = rWord32; + } else if (use_type & tBool) { + rep = rBit; + } else { + UNREACHABLE(); // should have at least a usage type! + } + } + // Preserve the usage type, but set the representation. + Type* upper = NodeProperties::GetBounds(node).upper; + SetOutput(node, rep | changer_->TypeFromUpperBound(upper)); + } + + Operator* Int32Op(Node* node) { + return changer_->Int32OperatorFor(node->opcode()); + } + + Operator* Uint32Op(Node* node) { + return changer_->Uint32OperatorFor(node->opcode()); + } + + Operator* Float64Op(Node* node) { + return changer_->Float64OperatorFor(node->opcode()); + } + + // Dispatching routine for visiting the node {node} with the usage {use}. + // Depending on the operator, propagate new usage info to the inputs. + void VisitNode(Node* node, RepTypeUnion use, SimplifiedLowering* lowering) { + switch (node->opcode()) { + //------------------------------------------------------------------ + // Common operators. + //------------------------------------------------------------------ + case IrOpcode::kStart: + case IrOpcode::kDead: + return VisitLeaf(node, 0); + case IrOpcode::kParameter: { + // TODO(titzer): use representation from linkage. + Type* upper = NodeProperties::GetBounds(node).upper; + ProcessInput(node, 0, 0); + SetOutput(node, rTagged | changer_->TypeFromUpperBound(upper)); + return; + } + case IrOpcode::kInt32Constant: + return VisitLeaf(node, rWord32); + case IrOpcode::kInt64Constant: + return VisitLeaf(node, rWord64); + case IrOpcode::kFloat64Constant: + return VisitLeaf(node, rFloat64); + case IrOpcode::kExternalConstant: + return VisitLeaf(node, rPtr); + case IrOpcode::kNumberConstant: + return VisitLeaf(node, rTagged); + case IrOpcode::kHeapConstant: + return VisitLeaf(node, rTagged); + + case IrOpcode::kEnd: + case IrOpcode::kIfTrue: + case IrOpcode::kIfFalse: + case IrOpcode::kReturn: + case IrOpcode::kMerge: + case IrOpcode::kThrow: + return VisitInputs(node); // default visit for all node inputs. + + case IrOpcode::kBranch: + ProcessInput(node, 0, rBit); + Enqueue(NodeProperties::GetControlInput(node, 0)); + break; + case IrOpcode::kPhi: + return VisitPhi(node, use); + +//------------------------------------------------------------------ +// JavaScript operators. +//------------------------------------------------------------------ +// For now, we assume that all JS operators were too complex to lower +// to Simplified and that they will always require tagged value inputs +// and produce tagged value outputs. +// TODO(turbofan): it might be possible to lower some JSOperators here, +// but that responsibility really lies in the typed lowering phase. +#define DEFINE_JS_CASE(x) case IrOpcode::k##x: + JS_OP_LIST(DEFINE_JS_CASE) +#undef DEFINE_JS_CASE + contains_js_nodes_ = true; + VisitInputs(node); + return SetOutput(node, rTagged); + + //------------------------------------------------------------------ + // Simplified operators. + //------------------------------------------------------------------ + case IrOpcode::kBooleanNot: { + if (lower()) { + RepTypeUnion input = GetInfo(node->InputAt(0))->output; + if (input & rBit) { + // BooleanNot(x: rBit) => WordEqual(x, #0) + node->set_op(lowering->machine()->WordEqual()); + node->AppendInput(jsgraph_->zone(), jsgraph_->Int32Constant(0)); + } else { + // BooleanNot(x: rTagged) => WordEqual(x, #false) + node->set_op(lowering->machine()->WordEqual()); + node->AppendInput(jsgraph_->zone(), jsgraph_->FalseConstant()); + } + } else { + // No input representation requirement; adapt during lowering. + ProcessInput(node, 0, tBool); + SetOutput(node, rBit); + } + break; + } + case IrOpcode::kNumberEqual: + case IrOpcode::kNumberLessThan: + case IrOpcode::kNumberLessThanOrEqual: { + // Number comparisons reduce to integer comparisons for integer inputs. + if (BothInputsAre(node, Type::Signed32())) { + // => signed Int32Cmp + VisitInt32Cmp(node); + if (lower()) node->set_op(Int32Op(node)); + } else if (BothInputsAre(node, Type::Unsigned32())) { + // => unsigned Int32Cmp + VisitUint32Cmp(node); + if (lower()) node->set_op(Uint32Op(node)); + } else { + // => Float64Cmp + VisitFloat64Cmp(node); + if (lower()) node->set_op(Float64Op(node)); + } + break; + } + case IrOpcode::kNumberAdd: + case IrOpcode::kNumberSubtract: { + // Add and subtract reduce to Int32Add/Sub if the inputs + // are already integers and all uses are truncating. + if (BothInputsAre(node, Type::Signed32()) && + (use & (tUint32 | tNumber | tAny)) == 0) { + // => signed Int32Add/Sub + VisitInt32Binop(node); + if (lower()) node->set_op(Int32Op(node)); + } else if (BothInputsAre(node, Type::Unsigned32()) && + (use & (tInt32 | tNumber | tAny)) == 0) { + // => unsigned Int32Add/Sub + VisitUint32Binop(node); + if (lower()) node->set_op(Uint32Op(node)); + } else { + // => Float64Add/Sub + VisitFloat64Binop(node); + if (lower()) node->set_op(Float64Op(node)); + } + break; + } + case IrOpcode::kNumberMultiply: + case IrOpcode::kNumberDivide: + case IrOpcode::kNumberModulus: { + // Float64Mul/Div/Mod + VisitFloat64Binop(node); + if (lower()) node->set_op(Float64Op(node)); + break; + } + case IrOpcode::kNumberToInt32: { + RepTypeUnion use_rep = use & rMask; + if (lower()) { + RepTypeUnion in = GetInfo(node->InputAt(0))->output; + if ((in & tMask) == tInt32 || (in & rMask) == rWord32) { + // If the input has type int32, or is already a word32, just change + // representation if necessary. + VisitUnop(node, tInt32 | use_rep, tInt32 | use_rep); + DeferReplacement(node, node->InputAt(0)); + } else { + // Require the input in float64 format and perform truncation. + // TODO(turbofan): could also avoid the truncation with a tag check. + VisitUnop(node, tInt32 | rFloat64, tInt32 | rWord32); + // TODO(titzer): should be a truncation. + node->set_op(lowering->machine()->ChangeFloat64ToInt32()); + } + } else { + // Propagate a type to the input, but pass through representation. + VisitUnop(node, tInt32, tInt32 | use_rep); + } + break; + } + case IrOpcode::kNumberToUint32: { + RepTypeUnion use_rep = use & rMask; + if (lower()) { + RepTypeUnion in = GetInfo(node->InputAt(0))->output; + if ((in & tMask) == tUint32 || (in & rMask) == rWord32) { + // The input has type int32, just change representation. + VisitUnop(node, tUint32 | use_rep, tUint32 | use_rep); + DeferReplacement(node, node->InputAt(0)); + } else { + // Require the input in float64 format to perform truncation. + // TODO(turbofan): could also avoid the truncation with a tag check. + VisitUnop(node, tUint32 | rFloat64, tUint32 | rWord32); + // TODO(titzer): should be a truncation. + node->set_op(lowering->machine()->ChangeFloat64ToUint32()); + } + } else { + // Propagate a type to the input, but pass through representation. + VisitUnop(node, tUint32, tUint32 | use_rep); + } + break; + } + case IrOpcode::kReferenceEqual: { + VisitBinop(node, kAnyTagged, rBit); + if (lower()) node->set_op(lowering->machine()->WordEqual()); + break; + } + case IrOpcode::kStringEqual: { + VisitBinop(node, kAnyTagged, rBit); + // TODO(titzer): lower StringEqual to stub/runtime call. + break; + } + case IrOpcode::kStringLessThan: { + VisitBinop(node, kAnyTagged, rBit); + // TODO(titzer): lower StringLessThan to stub/runtime call. + break; + } + case IrOpcode::kStringLessThanOrEqual: { + VisitBinop(node, kAnyTagged, rBit); + // TODO(titzer): lower StringLessThanOrEqual to stub/runtime call. + break; + } + case IrOpcode::kStringAdd: { + VisitBinop(node, kAnyTagged, kAnyTagged); + // TODO(titzer): lower StringAdd to stub/runtime call. + break; + } + case IrOpcode::kLoadField: { + FieldAccess access = FieldAccessOf(node->op()); + ProcessInput(node, 0, changer_->TypeForBasePointer(access)); + SetOutput(node, changer_->TypeForField(access)); + if (lower()) lowering->DoLoadField(node); + break; + } + case IrOpcode::kStoreField: { + FieldAccess access = FieldAccessOf(node->op()); + ProcessInput(node, 0, changer_->TypeForBasePointer(access)); + ProcessInput(node, 1, changer_->TypeForField(access)); + SetOutput(node, 0); + if (lower()) lowering->DoStoreField(node); + break; + } + case IrOpcode::kLoadElement: { + ElementAccess access = ElementAccessOf(node->op()); + ProcessInput(node, 0, changer_->TypeForBasePointer(access)); + ProcessInput(node, 1, kInt32); // element index + SetOutput(node, changer_->TypeForElement(access)); + if (lower()) lowering->DoLoadElement(node); + break; + } + case IrOpcode::kStoreElement: { + ElementAccess access = ElementAccessOf(node->op()); + ProcessInput(node, 0, changer_->TypeForBasePointer(access)); + ProcessInput(node, 1, kInt32); // element index + ProcessInput(node, 2, changer_->TypeForElement(access)); + SetOutput(node, 0); + if (lower()) lowering->DoStoreElement(node); + break; + } + + //------------------------------------------------------------------ + // Machine-level operators. + //------------------------------------------------------------------ + case IrOpcode::kLoad: { + // TODO(titzer): machine loads/stores need to know BaseTaggedness!? + RepType tBase = rTagged; + MachineRepresentation rep = OpParameter(node); + ProcessInput(node, 0, tBase); // pointer or object + ProcessInput(node, 1, kInt32); // index + SetOutput(node, changer_->TypeForMachineRepresentation(rep)); + break; + } + case IrOpcode::kStore: { + // TODO(titzer): machine loads/stores need to know BaseTaggedness!? + RepType tBase = rTagged; + StoreRepresentation rep = OpParameter(node); + ProcessInput(node, 0, tBase); // pointer or object + ProcessInput(node, 1, kInt32); // index + ProcessInput(node, 2, changer_->TypeForMachineRepresentation(rep.rep)); + SetOutput(node, 0); + break; + } + case IrOpcode::kWord32Shr: + // We output unsigned int32 for shift right because JavaScript. + return VisitBinop(node, rWord32, rWord32 | tUint32); + case IrOpcode::kWord32And: + case IrOpcode::kWord32Or: + case IrOpcode::kWord32Xor: + case IrOpcode::kWord32Shl: + case IrOpcode::kWord32Sar: + // We use signed int32 as the output type for these word32 operations, + // though the machine bits are the same for either signed or unsigned, + // because JavaScript considers the result from these operations signed. + return VisitBinop(node, rWord32, rWord32 | tInt32); + case IrOpcode::kWord32Equal: + return VisitBinop(node, rWord32, rBit); + + case IrOpcode::kInt32Add: + case IrOpcode::kInt32Sub: + case IrOpcode::kInt32Mul: + case IrOpcode::kInt32Div: + case IrOpcode::kInt32Mod: + return VisitInt32Binop(node); + case IrOpcode::kInt32UDiv: + case IrOpcode::kInt32UMod: + return VisitUint32Binop(node); + case IrOpcode::kInt32LessThan: + case IrOpcode::kInt32LessThanOrEqual: + return VisitInt32Cmp(node); + + case IrOpcode::kUint32LessThan: + case IrOpcode::kUint32LessThanOrEqual: + return VisitUint32Cmp(node); + + case IrOpcode::kInt64Add: + case IrOpcode::kInt64Sub: + case IrOpcode::kInt64Mul: + case IrOpcode::kInt64Div: + case IrOpcode::kInt64Mod: + return VisitInt64Binop(node); + case IrOpcode::kInt64LessThan: + case IrOpcode::kInt64LessThanOrEqual: + return VisitInt64Cmp(node); + + case IrOpcode::kInt64UDiv: + case IrOpcode::kInt64UMod: + return VisitUint64Binop(node); + + case IrOpcode::kWord64And: + case IrOpcode::kWord64Or: + case IrOpcode::kWord64Xor: + case IrOpcode::kWord64Shl: + case IrOpcode::kWord64Shr: + case IrOpcode::kWord64Sar: + return VisitBinop(node, rWord64, rWord64); + case IrOpcode::kWord64Equal: + return VisitBinop(node, rWord64, rBit); + + case IrOpcode::kConvertInt32ToInt64: + return VisitUnop(node, tInt32 | rWord32, tInt32 | rWord64); + case IrOpcode::kConvertInt64ToInt32: + return VisitUnop(node, tInt64 | rWord64, tInt32 | rWord32); + + case IrOpcode::kChangeInt32ToFloat64: + return VisitUnop(node, tInt32 | rWord32, tInt32 | rFloat64); + case IrOpcode::kChangeUint32ToFloat64: + return VisitUnop(node, tUint32 | rWord32, tUint32 | rFloat64); + case IrOpcode::kChangeFloat64ToInt32: + return VisitUnop(node, tInt32 | rFloat64, tInt32 | rWord32); + case IrOpcode::kChangeFloat64ToUint32: + return VisitUnop(node, tUint32 | rFloat64, tUint32 | rWord32); + + case IrOpcode::kFloat64Add: + case IrOpcode::kFloat64Sub: + case IrOpcode::kFloat64Mul: + case IrOpcode::kFloat64Div: + case IrOpcode::kFloat64Mod: + return VisitFloat64Binop(node); + case IrOpcode::kFloat64Equal: + case IrOpcode::kFloat64LessThan: + case IrOpcode::kFloat64LessThanOrEqual: + return VisitFloat64Cmp(node); + default: + VisitInputs(node); + break; + } + } + + void DeferReplacement(Node* node, Node* replacement) { + if (replacement->id() < count_) { + // Replace with a previously existing node eagerly. + node->ReplaceUses(replacement); + } else { + // Otherwise, we are replacing a node with a representation change. + // Such a substitution must be done after all lowering is done, because + // new nodes do not have {NodeInfo} entries, and that would confuse + // the representation change insertion for uses of it. + replacements_.push_back(node); + replacements_.push_back(replacement); + } + // TODO(titzer) node->RemoveAllInputs(); // Node is now dead. + } + + void PrintUseInfo(Node* node) { + TRACE(("#%d:%-20s ", node->id(), node->op()->mnemonic())); + PrintInfo(GetUseInfo(node)); + TRACE(("\n")); + } + + void PrintInfo(RepTypeUnion info) { + if (FLAG_trace_representation) { + char buf[REP_TYPE_STRLEN]; + RenderRepTypeUnion(buf, info); + TRACE(("%s", buf)); + } + } + + private: + JSGraph* jsgraph_; + int count_; // number of nodes in the graph + NodeInfo* info_; // node id -> usage information + NodeVector nodes_; // collected nodes + NodeVector replacements_; // replacements to be done after lowering + bool contains_js_nodes_; // {true} if a JS operator was seen + Phase phase_; // current phase of algorithm + RepresentationChanger* changer_; // for inserting representation changes + + std::queue > queue_; + + NodeInfo* GetInfo(Node* node) { + DCHECK(node->id() >= 0); + DCHECK(node->id() < count_); + return &info_[node->id()]; + } + + RepTypeUnion GetUseInfo(Node* node) { return GetInfo(node)->use; } +}; + + Node* SimplifiedLowering::IsTagged(Node* node) { // TODO(titzer): factor this out to a TaggingScheme abstraction. STATIC_ASSERT(kSmiTagMask == 1); // Only works if tag is the low bit. @@ -20,6 +712,17 @@ Node* SimplifiedLowering::IsTagged(Node* node) { } +void SimplifiedLowering::LowerAllNodes() { + SimplifiedOperatorBuilder simplified(graph()->zone()); + RepresentationChanger changer(jsgraph(), &simplified, machine(), + graph()->zone()->isolate()); + RepresentationSelector selector(jsgraph(), zone(), &changer); + selector.Run(this); + + LoweringBuilder::LowerAllNodes(); +} + + Node* SimplifiedLowering::Untag(Node* node) { // TODO(titzer): factor this out to a TaggingScheme abstraction. Node* shift_amount = jsgraph()->Int32Constant(kSmiTagSize + kSmiShiftSize); @@ -165,10 +868,8 @@ void SimplifiedLowering::DoChangeFloat64ToTagged(Node* node, Node* effect, void SimplifiedLowering::DoChangeBoolToBit(Node* node, Node* effect, Node* control) { - Node* val = node->InputAt(0); - Operator* op = - kPointerSize == 8 ? machine()->Word64Equal() : machine()->Word32Equal(); - Node* cmp = graph()->NewNode(op, val, jsgraph()->TrueConstant()); + Node* cmp = graph()->NewNode(machine()->WordEqual(), node->InputAt(0), + jsgraph()->TrueConstant()); node->ReplaceUses(cmp); } @@ -204,7 +905,7 @@ static WriteBarrierKind ComputeWriteBarrierKind( } -void SimplifiedLowering::DoLoadField(Node* node, Node* effect, Node* control) { +void SimplifiedLowering::DoLoadField(Node* node) { const FieldAccess& access = FieldAccessOf(node->op()); node->set_op(machine_.Load(access.representation)); Node* offset = jsgraph()->Int32Constant(access.offset - access.tag()); @@ -212,7 +913,7 @@ void SimplifiedLowering::DoLoadField(Node* node, Node* effect, Node* control) { } -void SimplifiedLowering::DoStoreField(Node* node, Node* effect, Node* control) { +void SimplifiedLowering::DoStoreField(Node* node) { const FieldAccess& access = FieldAccessOf(node->op()); WriteBarrierKind kind = ComputeWriteBarrierKind( access.base_is_tagged, access.representation, access.type); @@ -252,21 +953,19 @@ Node* SimplifiedLowering::ComputeIndex(const ElementAccess& access, } int fixed_offset = access.header_size - access.tag(); if (fixed_offset == 0) return index; - return graph()->NewNode(machine()->Int32Add(), - jsgraph()->Int32Constant(fixed_offset), index); + return graph()->NewNode(machine()->Int32Add(), index, + jsgraph()->Int32Constant(fixed_offset)); } -void SimplifiedLowering::DoLoadElement(Node* node, Node* effect, - Node* control) { +void SimplifiedLowering::DoLoadElement(Node* node) { const ElementAccess& access = ElementAccessOf(node->op()); node->set_op(machine_.Load(access.representation)); node->ReplaceInput(1, ComputeIndex(access, node->InputAt(1))); } -void SimplifiedLowering::DoStoreElement(Node* node, Node* effect, - Node* control) { +void SimplifiedLowering::DoStoreElement(Node* node) { const ElementAccess& access = ElementAccessOf(node->op()); WriteBarrierKind kind = ComputeWriteBarrierKind( access.base_is_tagged, access.representation, access.type); @@ -275,63 +974,37 @@ void SimplifiedLowering::DoStoreElement(Node* node, Node* effect, } -void SimplifiedLowering::Lower(Node* node) { - Node* start = graph()->start(); +void SimplifiedLowering::Lower(Node* node) {} + + +void SimplifiedLowering::LowerChange(Node* node, Node* effect, Node* control) { switch (node->opcode()) { - case IrOpcode::kBooleanNot: - case IrOpcode::kNumberEqual: - case IrOpcode::kNumberLessThan: - case IrOpcode::kNumberLessThanOrEqual: - case IrOpcode::kNumberAdd: - case IrOpcode::kNumberSubtract: - case IrOpcode::kNumberMultiply: - case IrOpcode::kNumberDivide: - case IrOpcode::kNumberModulus: - case IrOpcode::kNumberToInt32: - case IrOpcode::kNumberToUint32: - case IrOpcode::kReferenceEqual: - case IrOpcode::kStringEqual: - case IrOpcode::kStringLessThan: - case IrOpcode::kStringLessThanOrEqual: - case IrOpcode::kStringAdd: - break; case IrOpcode::kChangeTaggedToInt32: - DoChangeTaggedToUI32(node, start, start, true); + DoChangeTaggedToUI32(node, effect, control, true); break; case IrOpcode::kChangeTaggedToUint32: - DoChangeTaggedToUI32(node, start, start, false); + DoChangeTaggedToUI32(node, effect, control, false); break; case IrOpcode::kChangeTaggedToFloat64: - DoChangeTaggedToFloat64(node, start, start); + DoChangeTaggedToFloat64(node, effect, control); break; case IrOpcode::kChangeInt32ToTagged: - DoChangeUI32ToTagged(node, start, start, true); + DoChangeUI32ToTagged(node, effect, control, true); break; case IrOpcode::kChangeUint32ToTagged: - DoChangeUI32ToTagged(node, start, start, false); + DoChangeUI32ToTagged(node, effect, control, false); break; case IrOpcode::kChangeFloat64ToTagged: - DoChangeFloat64ToTagged(node, start, start); + DoChangeFloat64ToTagged(node, effect, control); break; case IrOpcode::kChangeBoolToBit: - DoChangeBoolToBit(node, start, start); + DoChangeBoolToBit(node, effect, control); break; case IrOpcode::kChangeBitToBool: - DoChangeBitToBool(node, start, start); - break; - case IrOpcode::kLoadField: - DoLoadField(node, start, start); - break; - case IrOpcode::kStoreField: - DoStoreField(node, start, start); - break; - case IrOpcode::kLoadElement: - DoLoadElement(node, start, start); - break; - case IrOpcode::kStoreElement: - DoStoreElement(node, start, start); + DoChangeBitToBool(node, effect, control); break; default: + UNREACHABLE(); break; } } diff --git a/src/compiler/simplified-lowering.h b/src/compiler/simplified-lowering.h index ed94db45ba..c85515d944 100644 --- a/src/compiler/simplified-lowering.h +++ b/src/compiler/simplified-lowering.h @@ -25,21 +25,16 @@ class SimplifiedLowering : public LoweringBuilder { machine_(jsgraph->zone()) {} virtual ~SimplifiedLowering() {} + void LowerAllNodes(); + virtual void Lower(Node* node); + void LowerChange(Node* node, Node* effect, Node* control); // TODO(titzer): These are exposed for direct testing. Use a friend class. - void DoChangeTaggedToUI32(Node* node, Node* effect, Node* control, - bool is_signed); - void DoChangeUI32ToTagged(Node* node, Node* effect, Node* control, - bool is_signed); - void DoChangeTaggedToFloat64(Node* node, Node* effect, Node* control); - void DoChangeFloat64ToTagged(Node* node, Node* effect, Node* control); - void DoChangeBoolToBit(Node* node, Node* effect, Node* control); - void DoChangeBitToBool(Node* node, Node* effect, Node* control); - void DoLoadField(Node* node, Node* effect, Node* control); - void DoStoreField(Node* node, Node* effect, Node* control); - void DoLoadElement(Node* node, Node* effect, Node* control); - void DoStoreElement(Node* node, Node* effect, Node* control); + void DoLoadField(Node* node); + void DoStoreField(Node* node); + void DoLoadElement(Node* node); + void DoStoreElement(Node* node); private: JSGraph* jsgraph_; @@ -49,9 +44,19 @@ class SimplifiedLowering : public LoweringBuilder { Node* IsTagged(Node* node); Node* Untag(Node* node); Node* OffsetMinusTagConstant(int32_t offset); - Node* ComputeIndex(const ElementAccess& access, Node* index); + void DoChangeTaggedToUI32(Node* node, Node* effect, Node* control, + bool is_signed); + void DoChangeUI32ToTagged(Node* node, Node* effect, Node* control, + bool is_signed); + void DoChangeTaggedToFloat64(Node* node, Node* effect, Node* control); + void DoChangeFloat64ToTagged(Node* node, Node* effect, Node* control); + void DoChangeBoolToBit(Node* node, Node* effect, Node* control); + void DoChangeBitToBool(Node* node, Node* effect, Node* control); + + friend class RepresentationSelector; + Zone* zone() { return jsgraph_->zone(); } JSGraph* jsgraph() { return jsgraph_; } Graph* graph() { return jsgraph()->graph(); } diff --git a/test/cctest/compiler/call-tester.h b/test/cctest/compiler/call-tester.h index 6998f1936a..c95547f953 100644 --- a/test/cctest/compiler/call-tester.h +++ b/test/cctest/compiler/call-tester.h @@ -106,6 +106,7 @@ struct ReturnValueTraits { UNREACHABLE(); return 0.0; } + static MachineRepresentation Representation() { return kMachineFloat64; } }; diff --git a/test/cctest/compiler/graph-builder-tester.h b/test/cctest/compiler/graph-builder-tester.h index 88bc966c70..747e99acb2 100644 --- a/test/cctest/compiler/graph-builder-tester.h +++ b/test/cctest/compiler/graph-builder-tester.h @@ -41,6 +41,8 @@ class MachineCallHelper : public CallHelper { Node* Parameter(int offset); + void GenerateCode() { Generate(); } + protected: virtual byte* Generate(); virtual void VerifyParameters(int parameter_count, @@ -71,7 +73,7 @@ class GraphAndBuilders { protected: // Prefixed with main_ to avoid naiming conflicts. - Graph* const main_graph_; + Graph* main_graph_; CommonOperatorBuilder main_common_; MachineOperatorBuilder main_machine_; SimplifiedOperatorBuilder main_simplified_; diff --git a/test/cctest/compiler/test-changes-lowering.cc b/test/cctest/compiler/test-changes-lowering.cc index 3eec14a91e..40849361c1 100644 --- a/test/cctest/compiler/test-changes-lowering.cc +++ b/test/cctest/compiler/test-changes-lowering.cc @@ -108,7 +108,7 @@ class ChangesLoweringTester : public GraphBuilderTester { this->start(), this->start()); Node* end = this->graph()->NewNode(this->common()->End(), ret); this->graph()->SetEnd(end); - this->lowering.Lower(change); + this->lowering.LowerChange(change, this->start(), this->start()); Verifier::Run(this->graph()); } @@ -124,7 +124,7 @@ class ChangesLoweringTester : public GraphBuilderTester { this->common()->Return(), this->Int32Constant(0), store, this->start()); Node* end = this->graph()->NewNode(this->common()->End(), ret); this->graph()->SetEnd(end); - this->lowering.Lower(change); + this->lowering.LowerChange(change, this->start(), this->start()); Verifier::Run(this->graph()); } @@ -139,7 +139,7 @@ class ChangesLoweringTester : public GraphBuilderTester { this->start(), this->start()); Node* end = this->graph()->NewNode(this->common()->End(), ret); this->graph()->SetEnd(end); - this->lowering.Lower(change); + this->lowering.LowerChange(change, this->start(), this->start()); Verifier::Run(this->graph()); } diff --git a/test/cctest/compiler/test-simplified-lowering.cc b/test/cctest/compiler/test-simplified-lowering.cc index bcf1531ca8..31550c9e6b 100644 --- a/test/cctest/compiler/test-simplified-lowering.cc +++ b/test/cctest/compiler/test-simplified-lowering.cc @@ -6,8 +6,10 @@ #include "src/compiler/control-builders.h" #include "src/compiler/generic-node-inl.h" +#include "src/compiler/graph-visualizer.h" #include "src/compiler/node-properties-inl.h" #include "src/compiler/pipeline.h" +#include "src/compiler/representation-change.h" #include "src/compiler/simplified-lowering.h" #include "src/compiler/simplified-node-factory.h" #include "src/compiler/typer.h" @@ -24,15 +26,14 @@ using namespace v8::internal; using namespace v8::internal::compiler; -// TODO(titzer): rename this to VMLoweringTester template -class SimplifiedGraphBuilderTester : public GraphBuilderTester { +class SimplifiedLoweringTester : public GraphBuilderTester { public: - SimplifiedGraphBuilderTester(MachineRepresentation p0 = kMachineLast, - MachineRepresentation p1 = kMachineLast, - MachineRepresentation p2 = kMachineLast, - MachineRepresentation p3 = kMachineLast, - MachineRepresentation p4 = kMachineLast) + SimplifiedLoweringTester(MachineRepresentation p0 = kMachineLast, + MachineRepresentation p1 = kMachineLast, + MachineRepresentation p2 = kMachineLast, + MachineRepresentation p3 = kMachineLast, + MachineRepresentation p4 = kMachineLast) : GraphBuilderTester(p0, p1, p2, p3, p4), typer(this->zone()), source_positions(this->graph()), @@ -44,37 +45,9 @@ class SimplifiedGraphBuilderTester : public GraphBuilderTester { JSGraph jsgraph; SimplifiedLowering lowering; - // Close graph and lower one node. - void Lower(Node* node) { + void LowerAllNodes() { this->End(); - if (node == NULL) { - lowering.LowerAllNodes(); - } else { - lowering.Lower(node); - } - } - - // Close graph and lower all nodes. - void LowerAllNodes() { Lower(NULL); } - - void StoreFloat64(Node* node, double* ptr) { - Node* ptr_node = this->PointerConstant(ptr); - this->Store(kMachineFloat64, ptr_node, node); - } - - Node* LoadInt32(int32_t* ptr) { - Node* ptr_node = this->PointerConstant(ptr); - return this->Load(kMachineWord32, ptr_node); - } - - Node* LoadUint32(uint32_t* ptr) { - Node* ptr_node = this->PointerConstant(ptr); - return this->Load(kMachineWord32, ptr_node); - } - - Node* LoadFloat64(double* ptr) { - Node* ptr_node = this->PointerConstant(ptr); - return this->Load(kMachineFloat64, ptr_node); + lowering.LowerAllNodes(); } Factory* factory() { return this->isolate()->factory(); } @@ -135,60 +108,63 @@ static Handle TestObject() { TEST(RunLoadMap) { - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); FieldAccess access = ForJSObjectMap(); Node* load = t.LoadField(access, t.Parameter(0)); t.Return(load); t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; - - Handle src = TestObject(); - Handle src_map(src->map()); - Object* result = t.Call(*src); - CHECK_EQ(*src_map, result); + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle src = TestObject(); + Handle src_map(src->map()); + Object* result = t.Call(*src); // TODO(titzer): raw pointers in call + CHECK_EQ(*src_map, result); + } } TEST(RunStoreMap) { - SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged, kMachineTagged); FieldAccess access = ForJSObjectMap(); t.StoreField(access, t.Parameter(1), t.Parameter(0)); - t.Return(t.Int32Constant(0)); + t.Return(t.jsgraph.TrueConstant()); t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; - - Handle src = TestObject(); - Handle src_map(src->map()); - Handle dst = TestObject(); - CHECK(src->map() != dst->map()); - t.Call(*src_map, *dst); - CHECK(*src_map == dst->map()); + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle src = TestObject(); + Handle src_map(src->map()); + Handle dst = TestObject(); + CHECK(src->map() != dst->map()); + t.Call(*src_map, *dst); // TODO(titzer): raw pointers in call + CHECK(*src_map == dst->map()); + } } TEST(RunLoadProperties) { - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); FieldAccess access = ForJSObjectProperties(); Node* load = t.LoadField(access, t.Parameter(0)); t.Return(load); t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; - - Handle src = TestObject(); - Handle src_props(src->properties()); - Object* result = t.Call(*src); - CHECK_EQ(*src_props, result); + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle src = TestObject(); + Handle src_props(src->properties()); + Object* result = t.Call(*src); // TODO(titzer): raw pointers in call + CHECK_EQ(*src_props, result); + } } TEST(RunLoadStoreMap) { - SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged, kMachineTagged); FieldAccess access = ForJSObjectMap(); Node* load = t.LoadField(access, t.Parameter(0)); t.StoreField(access, t.Parameter(1), load); @@ -196,21 +172,22 @@ TEST(RunLoadStoreMap) { t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; - - Handle src = TestObject(); - Handle src_map(src->map()); - Handle dst = TestObject(); - CHECK(src->map() != dst->map()); - Object* result = t.Call(*src, *dst); - CHECK(result->IsMap()); - CHECK_EQ(*src_map, result); - CHECK(*src_map == dst->map()); + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle src = TestObject(); + Handle src_map(src->map()); + Handle dst = TestObject(); + CHECK(src->map() != dst->map()); + Object* result = t.Call(*src, *dst); // TODO(titzer): raw pointers in call + CHECK(result->IsMap()); + CHECK_EQ(*src_map, result); + CHECK(*src_map == dst->map()); + } } TEST(RunLoadStoreFixedArrayIndex) { - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); ElementAccess access = ForFixedArrayElement(); Node* load = t.LoadElement(access, t.Parameter(0), t.Int32Constant(0)); t.StoreElement(access, t.Parameter(0), t.Int32Constant(1), load); @@ -218,101 +195,53 @@ TEST(RunLoadStoreFixedArrayIndex) { t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; - - Handle array = t.factory()->NewFixedArray(2); - Handle src = TestObject(); - Handle dst = TestObject(); - array->set(0, *src); - array->set(1, *dst); - Object* result = t.Call(*array); - CHECK_EQ(*src, result); - CHECK_EQ(*src, array->get(0)); - CHECK_EQ(*src, array->get(1)); + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle array = t.factory()->NewFixedArray(2); + Handle src = TestObject(); + Handle dst = TestObject(); + array->set(0, *src); + array->set(1, *dst); + Object* result = t.Call(*array); + CHECK_EQ(*src, result); + CHECK_EQ(*src, array->get(0)); + CHECK_EQ(*src, array->get(1)); + } } TEST(RunLoadStoreArrayBuffer) { - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); const int index = 12; - FieldAccess access = ForArrayBufferBackingStore(); - Node* backing_store = t.LoadField(access, t.Parameter(0)); ElementAccess buffer_access = ForBackingStoreElement(kMachineWord8); + Node* backing_store = + t.LoadField(ForArrayBufferBackingStore(), t.Parameter(0)); Node* load = t.LoadElement(buffer_access, backing_store, t.Int32Constant(index)); t.StoreElement(buffer_access, backing_store, t.Int32Constant(index + 1), load); - t.Return(load); + t.Return(t.jsgraph.TrueConstant()); t.LowerAllNodes(); - if (!Pipeline::SupportedTarget()) return; + if (Pipeline::SupportedTarget()) { + t.GenerateCode(); + Handle array = t.factory()->NewJSArrayBuffer(); + const int array_length = 2 * index; + Runtime::SetupArrayBufferAllocatingData(t.isolate(), array, array_length); + uint8_t* data = reinterpret_cast(array->backing_store()); + for (int i = 0; i < array_length; i++) { + data[i] = i; + } - Handle array = t.factory()->NewJSArrayBuffer(); - const int array_length = 2 * index; - Runtime::SetupArrayBufferAllocatingData(t.isolate(), array, array_length); - uint8_t* data = reinterpret_cast(array->backing_store()); - for (int i = 0; i < array_length; i++) { - data[i] = i; - } - int32_t result = t.Call(*array); - CHECK_EQ(index, result); - for (int i = 0; i < array_length; i++) { - uint8_t expected = i; - if (i == (index + 1)) expected = result; - CHECK_EQ(data[i], expected); - } -} - - -TEST(RunCopyFixedArray) { - SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); - - const int kArraySize = 15; - Node* one = t.Int32Constant(1); - Node* index = t.Int32Constant(0); - Node* limit = t.Int32Constant(kArraySize); - t.environment()->Push(index); - { - LoopBuilder loop(&t); - loop.BeginLoop(); - // Loop exit condition. - index = t.environment()->Top(); - Node* condition = t.Int32LessThan(index, limit); - loop.BreakUnless(condition); - // src[index] = dst[index]. - index = t.environment()->Pop(); - ElementAccess access = ForFixedArrayElement(); - Node* src = t.Parameter(0); - Node* load = t.LoadElement(access, src, index); - Node* dst = t.Parameter(1); - t.StoreElement(access, dst, index, load); - // index++ - index = t.Int32Add(index, one); - t.environment()->Push(index); - // continue. - loop.EndBody(); - loop.EndLoop(); - } - index = t.environment()->Pop(); - t.Return(index); - - t.LowerAllNodes(); - - if (!Pipeline::SupportedTarget()) return; - - Handle src = t.factory()->NewFixedArray(kArraySize); - Handle src_copy = t.factory()->NewFixedArray(kArraySize); - Handle dst = t.factory()->NewFixedArray(kArraySize); - for (int i = 0; i < kArraySize; i++) { - src->set(i, *TestObject()); - src_copy->set(i, src->get(i)); - dst->set(i, *TestObject()); - CHECK_NE(src_copy->get(i), dst->get(i)); - } - CHECK_EQ(kArraySize, t.Call(*src, *dst)); - for (int i = 0; i < kArraySize; i++) { - CHECK_EQ(src_copy->get(i), dst->get(i)); + // TODO(titzer): raw pointers in call + Object* result = t.Call(*array); + CHECK_EQ(t.isolate()->heap()->true_value(), result); + for (int i = 0; i < array_length; i++) { + uint8_t expected = i; + if (i == (index + 1)) expected = index; + CHECK_EQ(data[i], expected); + } } } @@ -325,7 +254,7 @@ TEST(RunLoadFieldFromUntaggedBase) { FieldAccess access = {kUntaggedBase, offset, Handle(), Type::Integral32(), kMachineTagged}; - SimplifiedGraphBuilderTester t; + SimplifiedLoweringTester t; Node* load = t.LoadField(access, t.PointerConstant(smis)); t.Return(load); t.LowerAllNodes(); @@ -349,7 +278,7 @@ TEST(RunStoreFieldToUntaggedBase) { FieldAccess access = {kUntaggedBase, offset, Handle(), Type::Integral32(), kMachineTagged}; - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); Node* p0 = t.Parameter(0); t.StoreField(access, t.PointerConstant(smis), p0); t.Return(p0); @@ -377,7 +306,7 @@ TEST(RunLoadElementFromUntaggedBase) { ElementAccess access = {kUntaggedBase, offset, Type::Integral32(), kMachineTagged}; - SimplifiedGraphBuilderTester t; + SimplifiedLoweringTester t; Node* load = t.LoadElement(access, t.PointerConstant(smis), t.Int32Constant(static_cast(j))); t.Return(load); @@ -405,7 +334,7 @@ TEST(RunStoreElementFromUntaggedBase) { ElementAccess access = {kUntaggedBase, offset, Type::Integral32(), kMachineTagged}; - SimplifiedGraphBuilderTester t(kMachineTagged); + SimplifiedLoweringTester t(kMachineTagged); Node* p0 = t.Parameter(0); t.StoreElement(access, t.PointerConstant(smis), t.Int32Constant(static_cast(j)), p0); @@ -425,3 +354,1019 @@ TEST(RunStoreElementFromUntaggedBase) { } } } + + +// A helper class for accessing fields and elements of various types, on both +// tagged and untagged base pointers. Contains both tagged and untagged buffers +// for testing direct memory access from generated code. +template +class AccessTester : public HandleAndZoneScope { + public: + bool tagged; + MachineRepresentation rep; + E* original_elements; + size_t num_elements; + E* untagged_array; + Handle tagged_array; // TODO(titzer): use FixedArray for tagged. + + AccessTester(bool t, MachineRepresentation r, E* orig, size_t num) + : tagged(t), + rep(r), + original_elements(orig), + num_elements(num), + untagged_array(static_cast(malloc(ByteSize()))), + tagged_array(main_isolate()->factory()->NewByteArray(ByteSize())) { + Reinitialize(); + } + + ~AccessTester() { free(untagged_array); } + + size_t ByteSize() { return num_elements * sizeof(E); } + + // Nuke both {untagged_array} and {tagged_array} with {original_elements}. + void Reinitialize() { + memcpy(untagged_array, original_elements, ByteSize()); + CHECK_EQ(ByteSize(), tagged_array->length()); + E* raw = reinterpret_cast(tagged_array->GetDataStartAddress()); + memcpy(raw, original_elements, ByteSize()); + } + + // Create and run code that copies the element in either {untagged_array} + // or {tagged_array} at index {from_index} to index {to_index}. + void RunCopyElement(int from_index, int to_index) { + // TODO(titzer): test element and field accesses where the base is not + // a constant in the code. + BoundsCheck(from_index); + BoundsCheck(to_index); + ElementAccess access = GetElementAccess(); + + SimplifiedLoweringTester t; + Node* ptr = GetBaseNode(&t); + Node* load = t.LoadElement(access, ptr, t.Int32Constant(from_index)); + t.StoreElement(access, ptr, t.Int32Constant(to_index), load); + t.Return(t.jsgraph.TrueConstant()); + t.LowerAllNodes(); + t.GenerateCode(); + + if (Pipeline::SupportedTarget()) { + Object* result = t.Call(); + CHECK_EQ(t.isolate()->heap()->true_value(), result); + } + } + + // Create and run code that copies the field in either {untagged_array} + // or {tagged_array} at index {from_index} to index {to_index}. + void RunCopyField(int from_index, int to_index) { + BoundsCheck(from_index); + BoundsCheck(to_index); + FieldAccess from_access = GetFieldAccess(from_index); + FieldAccess to_access = GetFieldAccess(to_index); + + SimplifiedLoweringTester t; + Node* ptr = GetBaseNode(&t); + Node* load = t.LoadField(from_access, ptr); + t.StoreField(to_access, ptr, load); + t.Return(t.jsgraph.TrueConstant()); + t.LowerAllNodes(); + t.GenerateCode(); + + if (Pipeline::SupportedTarget()) { + Object* result = t.Call(); + CHECK_EQ(t.isolate()->heap()->true_value(), result); + } + } + + // Create and run code that copies the elements from {this} to {that}. + void RunCopyElements(AccessTester* that) { + SimplifiedLoweringTester t; + + Node* one = t.Int32Constant(1); + Node* index = t.Int32Constant(0); + Node* limit = t.Int32Constant(static_cast(num_elements)); + t.environment()->Push(index); + Node* src = this->GetBaseNode(&t); + Node* dst = that->GetBaseNode(&t); + { + LoopBuilder loop(&t); + loop.BeginLoop(); + // Loop exit condition + index = t.environment()->Top(); + Node* condition = t.Int32LessThan(index, limit); + loop.BreakUnless(condition); + // dst[index] = src[index] + index = t.environment()->Pop(); + Node* load = t.LoadElement(this->GetElementAccess(), src, index); + t.StoreElement(that->GetElementAccess(), dst, index, load); + // index++ + index = t.Int32Add(index, one); + t.environment()->Push(index); + // continue + loop.EndBody(); + loop.EndLoop(); + } + index = t.environment()->Pop(); + t.Return(t.jsgraph.TrueConstant()); + t.LowerAllNodes(); + t.GenerateCode(); + + if (Pipeline::SupportedTarget()) { + Object* result = t.Call(); + CHECK_EQ(t.isolate()->heap()->true_value(), result); + } + } + + E GetElement(int index) { + BoundsCheck(index); + if (tagged) { + E* raw = reinterpret_cast(tagged_array->GetDataStartAddress()); + return raw[index]; + } else { + return untagged_array[index]; + } + } + + private: + ElementAccess GetElementAccess() { + ElementAccess access = {tagged ? kTaggedBase : kUntaggedBase, + tagged ? FixedArrayBase::kHeaderSize : 0, + Type::Any(), rep}; + return access; + } + + FieldAccess GetFieldAccess(int field) { + int offset = field * sizeof(E); + FieldAccess access = {tagged ? kTaggedBase : kUntaggedBase, + offset + (tagged ? FixedArrayBase::kHeaderSize : 0), + Handle(), Type::Any(), rep}; + return access; + } + + template + Node* GetBaseNode(SimplifiedLoweringTester* t) { + return tagged ? t->HeapConstant(tagged_array) + : t->PointerConstant(untagged_array); + } + + void BoundsCheck(int index) { + CHECK_GE(index, 0); + CHECK_LT(index, static_cast(num_elements)); + CHECK_EQ(ByteSize(), tagged_array->length()); + } +}; + + +template +static void RunAccessTest(MachineRepresentation rep, E* original_elements, + size_t num) { + int num_elements = static_cast(num); + + for (int taggedness = 0; taggedness < 2; taggedness++) { + AccessTester a(taggedness == 1, rep, original_elements, num); + for (int field = 0; field < 2; field++) { + for (int i = 0; i < num_elements - 1; i++) { + a.Reinitialize(); + if (field == 0) { + a.RunCopyField(i, i + 1); // Test field read/write. + } else { + a.RunCopyElement(i, i + 1); // Test element read/write. + } + if (Pipeline::SupportedTarget()) { // verify. + for (int j = 0; j < num_elements; j++) { + E expect = + j == (i + 1) ? original_elements[i] : original_elements[j]; + CHECK_EQ(expect, a.GetElement(j)); + } + } + } + } + } + // Test array copy. + for (int tf = 0; tf < 2; tf++) { + for (int tt = 0; tt < 2; tt++) { + AccessTester a(tf == 1, rep, original_elements, num); + AccessTester b(tt == 1, rep, original_elements, num); + a.RunCopyElements(&b); + if (Pipeline::SupportedTarget()) { // verify. + for (int i = 0; i < num_elements; i++) { + CHECK_EQ(a.GetElement(i), b.GetElement(i)); + } + } + } + } +} + + +TEST(RunAccessTests_uint8) { + uint8_t data[] = {0x07, 0x16, 0x25, 0x34, 0x43, 0x99, + 0xab, 0x78, 0x89, 0x19, 0x2b, 0x38}; + RunAccessTest(kMachineWord8, data, ARRAY_SIZE(data)); +} + + +TEST(RunAccessTests_uint16) { + uint16_t data[] = {0x071a, 0x162b, 0x253c, 0x344d, 0x435e, 0x7777}; + RunAccessTest(kMachineWord16, data, ARRAY_SIZE(data)); +} + + +TEST(RunAccessTests_int32) { + int32_t data[] = {-211, 211, 628347, 2000000000, -2000000000, -1, -100000034}; + RunAccessTest(kMachineWord32, data, ARRAY_SIZE(data)); +} + + +#define V8_2PART_INT64(a, b) (((static_cast(a) << 32) + 0x##b##u)) + + +TEST(RunAccessTests_int64) { + if (kPointerSize != 8) return; + int64_t data[] = {V8_2PART_INT64(0x10111213, 14151617), + V8_2PART_INT64(0x20212223, 24252627), + V8_2PART_INT64(0x30313233, 34353637), + V8_2PART_INT64(0xa0a1a2a3, a4a5a6a7), + V8_2PART_INT64(0xf0f1f2f3, f4f5f6f7)}; + RunAccessTest(kMachineWord64, data, ARRAY_SIZE(data)); +} + + +TEST(RunAccessTests_float64) { + double data[] = {1.25, -1.25, 2.75, 11.0, 11100.8}; + RunAccessTest(kMachineFloat64, data, ARRAY_SIZE(data)); +} + + +TEST(RunAccessTests_Smi) { + Smi* data[] = {Smi::FromInt(-1), Smi::FromInt(-9), + Smi::FromInt(0), Smi::FromInt(666), + Smi::FromInt(77777), Smi::FromInt(Smi::kMaxValue)}; + RunAccessTest(kMachineTagged, data, ARRAY_SIZE(data)); +} + + +// Fills in most of the nodes of the graph in order to make tests shorter. +class TestingGraph : public HandleAndZoneScope, public GraphAndBuilders { + public: + Typer typer; + JSGraph jsgraph; + Node* p0; + Node* p1; + Node* start; + Node* end; + Node* ret; + + TestingGraph(Type* p0_type, Type* p1_type = Type::None()) + : GraphAndBuilders(main_zone()), + typer(main_zone()), + jsgraph(graph(), common(), &typer) { + start = graph()->NewNode(common()->Start(2)); + graph()->SetStart(start); + ret = + graph()->NewNode(common()->Return(), jsgraph.Constant(0), start, start); + end = graph()->NewNode(common()->End(), ret); + graph()->SetEnd(end); + p0 = graph()->NewNode(common()->Parameter(0), start); + p1 = graph()->NewNode(common()->Parameter(1), start); + NodeProperties::SetBounds(p0, Bounds(p0_type)); + NodeProperties::SetBounds(p1, Bounds(p1_type)); + } + + void CheckLoweringBinop(IrOpcode::Value expected, Operator* op) { + Node* node = Return(graph()->NewNode(op, p0, p1)); + Lower(); + CHECK_EQ(expected, node->opcode()); + } + + void CheckLoweringTruncatedBinop(IrOpcode::Value expected, Operator* op, + Operator* trunc) { + Node* node = graph()->NewNode(op, p0, p1); + Return(graph()->NewNode(trunc, node)); + Lower(); + CHECK_EQ(expected, node->opcode()); + } + + void Lower() { + SimplifiedLowering lowering(&jsgraph, NULL); + lowering.LowerAllNodes(); + } + + // Inserts the node as the return value of the graph. + Node* Return(Node* node) { + ret->ReplaceInput(0, node); + return node; + } + + // Inserts the node as the effect input to the return of the graph. + void Effect(Node* node) { ret->ReplaceInput(1, node); } + + Node* ExampleWithOutput(RepType type) { + // TODO(titzer): use parameters with guaranteed representations. + if (type & tInt32) { + return graph()->NewNode(machine()->Int32Add(), jsgraph.Int32Constant(1), + jsgraph.Int32Constant(1)); + } else if (type & tUint32) { + return graph()->NewNode(machine()->Word32Shr(), jsgraph.Int32Constant(1), + jsgraph.Int32Constant(1)); + } else if (type & rFloat64) { + return graph()->NewNode(machine()->Float64Add(), + jsgraph.Float64Constant(1), + jsgraph.Float64Constant(1)); + } else if (type & rBit) { + return graph()->NewNode(machine()->Word32Equal(), + jsgraph.Int32Constant(1), + jsgraph.Int32Constant(1)); + } else if (type & rWord64) { + return graph()->NewNode(machine()->Int64Add(), Int64Constant(1), + Int64Constant(1)); + } else { + CHECK(type & rTagged); + return p0; + } + } + + Node* Use(Node* node, RepType type) { + if (type & tInt32) { + return graph()->NewNode(machine()->Int32LessThan(), node, + jsgraph.Int32Constant(1)); + } else if (type & tUint32) { + return graph()->NewNode(machine()->Uint32LessThan(), node, + jsgraph.Int32Constant(1)); + } else if (type & rFloat64) { + return graph()->NewNode(machine()->Float64Add(), node, + jsgraph.Float64Constant(1)); + } else if (type & rWord64) { + return graph()->NewNode(machine()->Int64LessThan(), node, + Int64Constant(1)); + } else { + return graph()->NewNode(simplified()->ReferenceEqual(Type::Any()), node, + jsgraph.TrueConstant()); + } + } + + Node* Branch(Node* cond) { + Node* br = graph()->NewNode(common()->Branch(), cond, start); + Node* tb = graph()->NewNode(common()->IfTrue(), br); + Node* fb = graph()->NewNode(common()->IfFalse(), br); + Node* m = graph()->NewNode(common()->Merge(2), tb, fb); + ret->ReplaceInput(NodeProperties::FirstControlIndex(ret), m); + return br; + } + + Node* Int64Constant(int64_t v) { + return graph()->NewNode(common()->Int64Constant(v)); + } + + SimplifiedOperatorBuilder* simplified() { return &main_simplified_; } + MachineOperatorBuilder* machine() { return &main_machine_; } + CommonOperatorBuilder* common() { return &main_common_; } + Graph* graph() { return main_graph_; } +}; + + +TEST(LowerBooleanNot_bit_bit) { + // BooleanNot(x: rBit) used as rBit + TestingGraph t(Type::Boolean()); + Node* b = t.ExampleWithOutput(rBit); + Node* inv = t.graph()->NewNode(t.simplified()->BooleanNot(), b); + Node* use = t.Branch(inv); + t.Lower(); + Node* cmp = use->InputAt(0); + CHECK_EQ(t.machine()->WordEqual()->opcode(), cmp->opcode()); + CHECK(b == cmp->InputAt(0) || b == cmp->InputAt(1)); + Node* f = t.jsgraph.Int32Constant(0); + CHECK(f == cmp->InputAt(0) || f == cmp->InputAt(1)); +} + + +TEST(LowerBooleanNot_bit_tagged) { + // BooleanNot(x: rBit) used as rTagged + TestingGraph t(Type::Boolean()); + Node* b = t.ExampleWithOutput(rBit); + Node* inv = t.graph()->NewNode(t.simplified()->BooleanNot(), b); + Node* use = t.Use(inv, rTagged); + t.Return(use); + t.Lower(); + CHECK_EQ(IrOpcode::kChangeBitToBool, use->InputAt(0)->opcode()); + Node* cmp = use->InputAt(0)->InputAt(0); + CHECK_EQ(t.machine()->WordEqual()->opcode(), cmp->opcode()); + CHECK(b == cmp->InputAt(0) || b == cmp->InputAt(1)); + Node* f = t.jsgraph.Int32Constant(0); + CHECK(f == cmp->InputAt(0) || f == cmp->InputAt(1)); +} + + +TEST(LowerBooleanNot_tagged_bit) { + // BooleanNot(x: rTagged) used as rBit + TestingGraph t(Type::Boolean()); + Node* b = t.p0; + Node* inv = t.graph()->NewNode(t.simplified()->BooleanNot(), b); + Node* use = t.Branch(inv); + t.Lower(); + Node* cmp = use->InputAt(0); + CHECK_EQ(t.machine()->WordEqual()->opcode(), cmp->opcode()); + CHECK(b == cmp->InputAt(0) || b == cmp->InputAt(1)); + Node* f = t.jsgraph.FalseConstant(); + CHECK(f == cmp->InputAt(0) || f == cmp->InputAt(1)); +} + + +TEST(LowerBooleanNot_tagged_tagged) { + // BooleanNot(x: rTagged) used as rTagged + TestingGraph t(Type::Boolean()); + Node* b = t.p0; + Node* inv = t.graph()->NewNode(t.simplified()->BooleanNot(), b); + Node* use = t.Use(inv, rTagged); + t.Return(use); + t.Lower(); + CHECK_EQ(IrOpcode::kChangeBitToBool, use->InputAt(0)->opcode()); + Node* cmp = use->InputAt(0)->InputAt(0); + CHECK_EQ(t.machine()->WordEqual()->opcode(), cmp->opcode()); + CHECK(b == cmp->InputAt(0) || b == cmp->InputAt(1)); + Node* f = t.jsgraph.FalseConstant(); + CHECK(f == cmp->InputAt(0) || f == cmp->InputAt(1)); +} + + +static Type* test_types[] = {Type::Signed32(), Type::Unsigned32(), + Type::Number(), Type::Any()}; + + +TEST(LowerNumberCmp_to_int32) { + TestingGraph t(Type::Signed32(), Type::Signed32()); + + t.CheckLoweringBinop(IrOpcode::kWord32Equal, t.simplified()->NumberEqual()); + t.CheckLoweringBinop(IrOpcode::kInt32LessThan, + t.simplified()->NumberLessThan()); + t.CheckLoweringBinop(IrOpcode::kInt32LessThanOrEqual, + t.simplified()->NumberLessThanOrEqual()); +} + + +TEST(LowerNumberCmp_to_uint32) { + TestingGraph t(Type::Unsigned32(), Type::Unsigned32()); + + t.CheckLoweringBinop(IrOpcode::kWord32Equal, t.simplified()->NumberEqual()); + t.CheckLoweringBinop(IrOpcode::kUint32LessThan, + t.simplified()->NumberLessThan()); + t.CheckLoweringBinop(IrOpcode::kUint32LessThanOrEqual, + t.simplified()->NumberLessThanOrEqual()); +} + + +TEST(LowerNumberCmp_to_float64) { + static Type* types[] = {Type::Number(), Type::Any()}; + + for (size_t i = 0; i < ARRAY_SIZE(types); i++) { + TestingGraph t(types[i], types[i]); + + t.CheckLoweringBinop(IrOpcode::kFloat64Equal, + t.simplified()->NumberEqual()); + t.CheckLoweringBinop(IrOpcode::kFloat64LessThan, + t.simplified()->NumberLessThan()); + t.CheckLoweringBinop(IrOpcode::kFloat64LessThanOrEqual, + t.simplified()->NumberLessThanOrEqual()); + } +} + + +TEST(LowerNumberAddSub_to_int32) { + TestingGraph t(Type::Signed32(), Type::Signed32()); + t.CheckLoweringTruncatedBinop(IrOpcode::kInt32Add, + t.simplified()->NumberAdd(), + t.simplified()->NumberToInt32()); + t.CheckLoweringTruncatedBinop(IrOpcode::kInt32Sub, + t.simplified()->NumberSubtract(), + t.simplified()->NumberToInt32()); +} + + +TEST(LowerNumberAddSub_to_uint32) { + TestingGraph t(Type::Unsigned32(), Type::Unsigned32()); + t.CheckLoweringTruncatedBinop(IrOpcode::kInt32Add, + t.simplified()->NumberAdd(), + t.simplified()->NumberToUint32()); + t.CheckLoweringTruncatedBinop(IrOpcode::kInt32Sub, + t.simplified()->NumberSubtract(), + t.simplified()->NumberToUint32()); +} + + +TEST(LowerNumberAddSub_to_float64) { + for (size_t i = 0; i < ARRAY_SIZE(test_types); i++) { + TestingGraph t(test_types[i], test_types[i]); + + t.CheckLoweringBinop(IrOpcode::kFloat64Add, t.simplified()->NumberAdd()); + t.CheckLoweringBinop(IrOpcode::kFloat64Sub, + t.simplified()->NumberSubtract()); + } +} + + +TEST(LowerNumberDivMod_to_float64) { + for (size_t i = 0; i < ARRAY_SIZE(test_types); i++) { + TestingGraph t(test_types[i], test_types[i]); + + t.CheckLoweringBinop(IrOpcode::kFloat64Div, t.simplified()->NumberDivide()); + t.CheckLoweringBinop(IrOpcode::kFloat64Mod, + t.simplified()->NumberModulus()); + } +} + + +static void CheckChangeOf(IrOpcode::Value change, Node* of, Node* node) { + CHECK_EQ(change, node->opcode()); + CHECK_EQ(of, node->InputAt(0)); +} + + +TEST(LowerNumberToInt32_to_nop) { + // NumberToInt32(x: rTagged | tInt32) used as rTagged + TestingGraph t(Type::Signed32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToInt32(), t.p0); + Node* use = t.Use(trunc, rTagged); + t.Return(use); + t.Lower(); + CHECK_EQ(t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToInt32_to_ChangeTaggedToFloat64) { + // NumberToInt32(x: rTagged | tInt32) used as rFloat64 + TestingGraph t(Type::Signed32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToInt32(), t.p0); + Node* use = t.Use(trunc, rFloat64); + t.Return(use); + t.Lower(); + CheckChangeOf(IrOpcode::kChangeTaggedToFloat64, t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToInt32_to_ChangeTaggedToInt32) { + // NumberToInt32(x: rTagged | tInt32) used as rWord32 + TestingGraph t(Type::Signed32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToInt32(), t.p0); + Node* use = t.Use(trunc, tInt32); + t.Return(use); + t.Lower(); + CheckChangeOf(IrOpcode::kChangeTaggedToInt32, t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToInt32_to_ChangeFloat64ToTagged) { + // TODO(titzer): NumberToInt32(x: rFloat64 | tInt32) used as rTagged +} + + +TEST(LowerNumberToInt32_to_ChangeFloat64ToInt32) { + // TODO(titzer): NumberToInt32(x: rFloat64 | tInt32) used as rWord32 | tInt32 +} + + +TEST(LowerNumberToInt32_to_TruncateFloat64ToInt32) { + // TODO(titzer): NumberToInt32(x: rFloat64) used as rWord32 | tUint32 +} + + +TEST(LowerNumberToUint32_to_nop) { + // NumberToUint32(x: rTagged | tUint32) used as rTagged + TestingGraph t(Type::Unsigned32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToUint32(), t.p0); + Node* use = t.Use(trunc, rTagged); + t.Return(use); + t.Lower(); + CHECK_EQ(t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToUint32_to_ChangeTaggedToFloat64) { + // NumberToUint32(x: rTagged | tUint32) used as rWord32 + TestingGraph t(Type::Unsigned32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToUint32(), t.p0); + Node* use = t.Use(trunc, rFloat64); + t.Return(use); + t.Lower(); + CheckChangeOf(IrOpcode::kChangeTaggedToFloat64, t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToUint32_to_ChangeTaggedToUint32) { + // NumberToUint32(x: rTagged | tUint32) used as rWord32 + TestingGraph t(Type::Unsigned32()); + Node* trunc = t.graph()->NewNode(t.simplified()->NumberToUint32(), t.p0); + Node* use = t.Use(trunc, tUint32); + t.Return(use); + t.Lower(); + CheckChangeOf(IrOpcode::kChangeTaggedToUint32, t.p0, use->InputAt(0)); +} + + +TEST(LowerNumberToUint32_to_ChangeFloat64ToTagged) { + // TODO(titzer): NumberToUint32(x: rFloat64 | tUint32) used as rTagged +} + + +TEST(LowerNumberToUint32_to_ChangeFloat64ToUint32) { + // TODO(titzer): NumberToUint32(x: rFloat64 | tUint32) used as rWord32 +} + + +TEST(LowerNumberToUint32_to_TruncateFloat64ToUint32) { + // TODO(titzer): NumberToUint32(x: rFloat64) used as rWord32 +} + + +TEST(LowerReferenceEqual_to_wordeq) { + TestingGraph t(Type::Any(), Type::Any()); + IrOpcode::Value opcode = + static_cast(t.machine()->WordEqual()->opcode()); + t.CheckLoweringBinop(opcode, t.simplified()->ReferenceEqual(Type::Any())); +} + + +TEST(LowerStringOps_to_rtcalls) { + if (false) { // TODO(titzer): lower StringOps to runtime calls + TestingGraph t(Type::String(), Type::String()); + t.CheckLoweringBinop(IrOpcode::kCall, t.simplified()->StringEqual()); + t.CheckLoweringBinop(IrOpcode::kCall, t.simplified()->StringLessThan()); + t.CheckLoweringBinop(IrOpcode::kCall, + t.simplified()->StringLessThanOrEqual()); + t.CheckLoweringBinop(IrOpcode::kCall, t.simplified()->StringAdd()); + } +} + + +void CheckChangeInsertion(IrOpcode::Value expected, RepType from, RepType to) { + TestingGraph t(Type::Any()); + Node* in = t.ExampleWithOutput(from); + Node* use = t.Use(in, to); + t.Return(use); + t.Lower(); + CHECK_EQ(expected, use->InputAt(0)->opcode()); + CHECK_EQ(in, use->InputAt(0)->InputAt(0)); +} + + +TEST(InsertBasicChanges) { + if (false) { + // TODO(titzer): these changes need the output to have the right type. + CheckChangeInsertion(IrOpcode::kChangeFloat64ToInt32, rFloat64, tInt32); + CheckChangeInsertion(IrOpcode::kChangeFloat64ToUint32, rFloat64, tUint32); + CheckChangeInsertion(IrOpcode::kChangeTaggedToInt32, rTagged, tInt32); + CheckChangeInsertion(IrOpcode::kChangeTaggedToUint32, rTagged, tUint32); + } + + CheckChangeInsertion(IrOpcode::kChangeFloat64ToTagged, rFloat64, rTagged); + CheckChangeInsertion(IrOpcode::kChangeTaggedToFloat64, rTagged, rFloat64); + + CheckChangeInsertion(IrOpcode::kChangeInt32ToFloat64, tInt32, rFloat64); + CheckChangeInsertion(IrOpcode::kChangeInt32ToTagged, tInt32, rTagged); + + CheckChangeInsertion(IrOpcode::kChangeUint32ToFloat64, tUint32, rFloat64); + CheckChangeInsertion(IrOpcode::kChangeUint32ToTagged, tUint32, rTagged); +} + + +static void CheckChangesAroundBinop(TestingGraph* t, Operator* op, + IrOpcode::Value input_change, + IrOpcode::Value output_change) { + Node* binop = t->graph()->NewNode(op, t->p0, t->p1); + t->Return(binop); + t->Lower(); + CHECK_EQ(input_change, binop->InputAt(0)->opcode()); + CHECK_EQ(input_change, binop->InputAt(1)->opcode()); + CHECK_EQ(t->p0, binop->InputAt(0)->InputAt(0)); + CHECK_EQ(t->p1, binop->InputAt(1)->InputAt(0)); + CHECK_EQ(output_change, t->ret->InputAt(0)->opcode()); + CHECK_EQ(binop, t->ret->InputAt(0)->InputAt(0)); +} + + +TEST(InsertChangesAroundInt32Binops) { + TestingGraph t(Type::Signed32(), Type::Signed32()); + + Operator* ops[] = {t.machine()->Int32Add(), t.machine()->Int32Sub(), + t.machine()->Int32Mul(), t.machine()->Int32Div(), + t.machine()->Int32Mod(), t.machine()->Word32And(), + t.machine()->Word32Or(), t.machine()->Word32Xor(), + t.machine()->Word32Shl(), t.machine()->Word32Sar()}; + + for (size_t i = 0; i < ARRAY_SIZE(ops); i++) { + CheckChangesAroundBinop(&t, ops[i], IrOpcode::kChangeTaggedToInt32, + IrOpcode::kChangeInt32ToTagged); + } +} + + +TEST(InsertChangesAroundInt32Cmp) { + TestingGraph t(Type::Signed32(), Type::Signed32()); + + Operator* ops[] = {t.machine()->Int32LessThan(), + t.machine()->Int32LessThanOrEqual()}; + + for (size_t i = 0; i < ARRAY_SIZE(ops); i++) { + CheckChangesAroundBinop(&t, ops[i], IrOpcode::kChangeTaggedToInt32, + IrOpcode::kChangeBitToBool); + } +} + + +TEST(InsertChangesAroundUint32Cmp) { + TestingGraph t(Type::Unsigned32(), Type::Unsigned32()); + + Operator* ops[] = {t.machine()->Uint32LessThan(), + t.machine()->Uint32LessThanOrEqual()}; + + for (size_t i = 0; i < ARRAY_SIZE(ops); i++) { + CheckChangesAroundBinop(&t, ops[i], IrOpcode::kChangeTaggedToUint32, + IrOpcode::kChangeBitToBool); + } +} + + +TEST(InsertChangesAroundFloat64Binops) { + TestingGraph t(Type::Number(), Type::Number()); + + Operator* ops[] = { + t.machine()->Float64Add(), t.machine()->Float64Sub(), + t.machine()->Float64Mul(), t.machine()->Float64Div(), + t.machine()->Float64Mod(), + }; + + for (size_t i = 0; i < ARRAY_SIZE(ops); i++) { + CheckChangesAroundBinop(&t, ops[i], IrOpcode::kChangeTaggedToFloat64, + IrOpcode::kChangeFloat64ToTagged); + } +} + + +TEST(InsertChangesAroundFloat64Cmp) { + TestingGraph t(Type::Number(), Type::Number()); + + Operator* ops[] = {t.machine()->Float64Equal(), + t.machine()->Float64LessThan(), + t.machine()->Float64LessThanOrEqual()}; + + for (size_t i = 0; i < ARRAY_SIZE(ops); i++) { + CheckChangesAroundBinop(&t, ops[i], IrOpcode::kChangeTaggedToFloat64, + IrOpcode::kChangeBitToBool); + } +} + + +void CheckFieldAccessArithmetic(FieldAccess access, Node* load_or_store) { + Int32Matcher index = Int32Matcher(load_or_store->InputAt(1)); + CHECK(index.Is(access.offset - access.tag())); +} + + +Node* CheckElementAccessArithmetic(ElementAccess access, Node* load_or_store) { + Int32BinopMatcher index(load_or_store->InputAt(1)); + CHECK_EQ(IrOpcode::kInt32Add, index.node()->opcode()); + CHECK(index.right().Is(access.header_size - access.tag())); + + int element_size = 0; + switch (access.representation) { + case kMachineTagged: + element_size = kPointerSize; + break; + case kMachineWord8: + element_size = 1; + break; + case kMachineWord16: + element_size = 2; + break; + case kMachineWord32: + element_size = 4; + break; + case kMachineWord64: + case kMachineFloat64: + element_size = 8; + break; + case kMachineLast: + UNREACHABLE(); + break; + } + + if (element_size != 1) { + Int32BinopMatcher mul(index.left().node()); + CHECK_EQ(IrOpcode::kInt32Mul, mul.node()->opcode()); + CHECK(mul.right().Is(element_size)); + return mul.left().node(); + } else { + return index.left().node(); + } +} + + +static const MachineRepresentation machine_reps[] = { + kMachineWord8, kMachineWord16, kMachineWord32, + kMachineWord64, kMachineFloat64, kMachineTagged}; + + +// Representation types corresponding to those above. +static const RepType rep_types[] = {static_cast(rWord32 | tUint32), + static_cast(rWord32 | tUint32), + static_cast(rWord32 | tInt32), + static_cast(rWord64), + static_cast(rFloat64 | tNumber), + static_cast(rTagged | tAny)}; + + +TEST(LowerLoadField_to_load) { + TestingGraph t(Type::Any(), Type::Signed32()); + + for (size_t i = 0; i < ARRAY_SIZE(machine_reps); i++) { + FieldAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Handle::null(), Type::Any(), machine_reps[i]}; + + Node* load = + t.graph()->NewNode(t.simplified()->LoadField(access), t.p0, t.start); + Node* use = t.Use(load, rep_types[i]); + t.Return(use); + t.Lower(); + CHECK_EQ(IrOpcode::kLoad, load->opcode()); + CHECK_EQ(t.p0, load->InputAt(0)); + CheckFieldAccessArithmetic(access, load); + + MachineRepresentation rep = OpParameter(load); + CHECK_EQ(machine_reps[i], rep); + } +} + + +TEST(LowerStoreField_to_store) { + TestingGraph t(Type::Any(), Type::Signed32()); + + for (size_t i = 0; i < ARRAY_SIZE(machine_reps); i++) { + FieldAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Handle::null(), Type::Any(), machine_reps[i]}; + + + Node* val = t.ExampleWithOutput(rep_types[i]); + Node* store = t.graph()->NewNode(t.simplified()->StoreField(access), t.p0, + val, t.start, t.start); + t.Effect(store); + t.Lower(); + CHECK_EQ(IrOpcode::kStore, store->opcode()); + CHECK_EQ(val, store->InputAt(2)); + CheckFieldAccessArithmetic(access, store); + + StoreRepresentation rep = OpParameter(store); + if (rep_types[i] & rTagged) { + CHECK_EQ(kFullWriteBarrier, rep.write_barrier_kind); + } + CHECK_EQ(machine_reps[i], rep.rep); + } +} + + +TEST(LowerLoadElement_to_load) { + TestingGraph t(Type::Any(), Type::Signed32()); + + for (size_t i = 0; i < ARRAY_SIZE(machine_reps); i++) { + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Type::Any(), machine_reps[i]}; + + Node* load = t.graph()->NewNode(t.simplified()->LoadElement(access), t.p0, + t.p1, t.start); + Node* use = t.Use(load, rep_types[i]); + t.Return(use); + t.Lower(); + CHECK_EQ(IrOpcode::kLoad, load->opcode()); + CHECK_EQ(t.p0, load->InputAt(0)); + CheckElementAccessArithmetic(access, load); + + MachineRepresentation rep = OpParameter(load); + CHECK_EQ(machine_reps[i], rep); + } +} + + +TEST(LowerStoreElement_to_store) { + TestingGraph t(Type::Any(), Type::Signed32()); + + for (size_t i = 0; i < ARRAY_SIZE(machine_reps); i++) { + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Type::Any(), machine_reps[i]}; + + Node* val = t.ExampleWithOutput(rep_types[i]); + Node* store = t.graph()->NewNode(t.simplified()->StoreElement(access), t.p0, + t.p1, val, t.start, t.start); + t.Effect(store); + t.Lower(); + CHECK_EQ(IrOpcode::kStore, store->opcode()); + CHECK_EQ(val, store->InputAt(2)); + CheckElementAccessArithmetic(access, store); + + StoreRepresentation rep = OpParameter(store); + if (rep_types[i] & rTagged) { + CHECK_EQ(kFullWriteBarrier, rep.write_barrier_kind); + } + CHECK_EQ(machine_reps[i], rep.rep); + } +} + + +TEST(InsertChangeForLoadElementIndex) { + // LoadElement(obj: Tagged, index: tInt32 | rTagged) => + // Load(obj, Int32Add(Int32Mul(ChangeTaggedToInt32(index), #k), #k)) + TestingGraph t(Type::Any(), Type::Signed32()); + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, Type::Any(), + kMachineTagged}; + + Node* load = t.graph()->NewNode(t.simplified()->LoadElement(access), t.p0, + t.p1, t.start); + t.Return(load); + t.Lower(); + CHECK_EQ(IrOpcode::kLoad, load->opcode()); + CHECK_EQ(t.p0, load->InputAt(0)); + + Node* index = CheckElementAccessArithmetic(access, load); + CheckChangeOf(IrOpcode::kChangeTaggedToInt32, t.p1, index); +} + + +TEST(InsertChangeForStoreElementIndex) { + // StoreElement(obj: Tagged, index: tInt32 | rTagged, val) => + // Store(obj, Int32Add(Int32Mul(ChangeTaggedToInt32(index), #k), #k), val) + TestingGraph t(Type::Any(), Type::Signed32()); + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, Type::Any(), + kMachineTagged}; + + Node* store = + t.graph()->NewNode(t.simplified()->StoreElement(access), t.p0, t.p1, + t.jsgraph.TrueConstant(), t.start, t.start); + t.Effect(store); + t.Lower(); + CHECK_EQ(IrOpcode::kStore, store->opcode()); + CHECK_EQ(t.p0, store->InputAt(0)); + + Node* index = CheckElementAccessArithmetic(access, store); + CheckChangeOf(IrOpcode::kChangeTaggedToInt32, t.p1, index); +} + + +TEST(InsertChangeForLoadElement) { + // TODO(titzer): test all load/store representation change insertions. + TestingGraph t(Type::Any(), Type::Signed32()); + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, Type::Any(), + kMachineFloat64}; + + Node* load = t.graph()->NewNode(t.simplified()->LoadElement(access), t.p0, + t.p1, t.start); + t.Return(load); + t.Lower(); + CHECK_EQ(IrOpcode::kLoad, load->opcode()); + CHECK_EQ(t.p0, load->InputAt(0)); + CheckChangeOf(IrOpcode::kChangeFloat64ToTagged, load, t.ret->InputAt(0)); +} + + +TEST(InsertChangeForLoadField) { + // TODO(titzer): test all load/store representation change insertions. + TestingGraph t(Type::Any(), Type::Signed32()); + FieldAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Handle::null(), Type::Any(), kMachineFloat64}; + + Node* load = + t.graph()->NewNode(t.simplified()->LoadField(access), t.p0, t.start); + t.Return(load); + t.Lower(); + CHECK_EQ(IrOpcode::kLoad, load->opcode()); + CHECK_EQ(t.p0, load->InputAt(0)); + CheckChangeOf(IrOpcode::kChangeFloat64ToTagged, load, t.ret->InputAt(0)); +} + + +TEST(InsertChangeForStoreElement) { + // TODO(titzer): test all load/store representation change insertions. + TestingGraph t(Type::Any(), Type::Signed32()); + ElementAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, Type::Any(), + kMachineFloat64}; + + Node* store = + t.graph()->NewNode(t.simplified()->StoreElement(access), t.p0, + t.jsgraph.Int32Constant(0), t.p1, t.start, t.start); + t.Effect(store); + t.Lower(); + + CHECK_EQ(IrOpcode::kStore, store->opcode()); + CHECK_EQ(t.p0, store->InputAt(0)); + CheckChangeOf(IrOpcode::kChangeTaggedToFloat64, t.p1, store->InputAt(2)); +} + + +TEST(InsertChangeForStoreField) { + // TODO(titzer): test all load/store representation change insertions. + TestingGraph t(Type::Any(), Type::Signed32()); + FieldAccess access = {kTaggedBase, FixedArrayBase::kHeaderSize, + Handle::null(), Type::Any(), kMachineFloat64}; + + Node* store = t.graph()->NewNode(t.simplified()->StoreField(access), t.p0, + t.p1, t.start, t.start); + t.Effect(store); + t.Lower(); + + CHECK_EQ(IrOpcode::kStore, store->opcode()); + CHECK_EQ(t.p0, store->InputAt(0)); + CheckChangeOf(IrOpcode::kChangeTaggedToFloat64, t.p1, store->InputAt(2)); +}