OLD | NEW |
---|---|
1 // Copyright 2014 the V8 project authors. All rights reserved. | 1 // Copyright 2014 the V8 project authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "src/compiler/gap-resolver.h" | 5 #include "src/compiler/gap-resolver.h" |
6 | 6 |
7 #include "src/base/utils/random-number-generator.h" | 7 #include "src/base/utils/random-number-generator.h" |
8 #include "test/cctest/cctest.h" | 8 #include "test/cctest/cctest.h" |
9 | 9 |
10 namespace v8 { | 10 namespace v8 { |
11 namespace internal { | 11 namespace internal { |
12 namespace compiler { | 12 namespace compiler { |
13 | 13 |
14 const auto GetRegConfig = RegisterConfiguration::Turbofan; | |
15 | |
16 bool IsFPLocation(const InstructionOperand& op) { | |
Mircea Trofin
2016/09/30 04:56:13
don't we have this defined on InstructionOperand?
bbudge
2016/09/30 18:16:31
Since the only usage is in this test, I just imple
bbudge
2016/10/03 23:45:50
I merged in the IsFPLocationOperand method from my
| |
17 return op.IsFPStackSlot() || op.IsFPRegister(); | |
18 } | |
19 | |
20 // Fragments the given operand into an equivalent set of operands to simplify | |
21 // ParallelMove equivalence testing. | |
22 void GetCanonicalOperands(const InstructionOperand& op, | |
23 std::vector<InstructionOperand>* fragments) { | |
24 CHECK(!kSimpleFPAliasing); | |
25 CHECK(IsFPLocation(op)); | |
26 // TODO(bbudge) Split into float operands on platforms with non-simple FP | |
27 // register aliasing. | |
28 fragments->push_back(op); | |
29 } | |
30 | |
14 // The state of our move interpreter is the mapping of operands to values. Note | 31 // The state of our move interpreter is the mapping of operands to values. Note |
15 // that the actual values don't really matter, all we care about is equality. | 32 // that the actual values don't really matter, all we care about is equality. |
16 class InterpreterState { | 33 class InterpreterState { |
17 public: | 34 public: |
18 void ExecuteInParallel(const ParallelMove* moves) { | 35 void ExecuteInParallel(const ParallelMove* moves) { |
19 InterpreterState copy(*this); | 36 InterpreterState copy(*this); |
20 for (const auto m : *moves) { | 37 for (const auto m : *moves) { |
21 if (!m->IsRedundant()) write(m->destination(), copy.read(m->source())); | 38 CHECK(!m->IsRedundant()); |
39 const InstructionOperand& src = m->source(); | |
40 const InstructionOperand& dst = m->destination(); | |
41 if (!kSimpleFPAliasing && IsFPLocation(src) && IsFPLocation(dst)) { | |
42 // Canonicalize FP location-location moves. | |
43 std::vector<InstructionOperand> src_fragments; | |
44 GetCanonicalOperands(src, &src_fragments); | |
45 CHECK(!src_fragments.empty()); | |
46 std::vector<InstructionOperand> dst_fragments; | |
47 GetCanonicalOperands(dst, &dst_fragments); | |
48 CHECK_EQ(src_fragments.size(), dst_fragments.size()); | |
49 | |
50 for (size_t i = 0; i < src_fragments.size(); ++i) { | |
51 write(dst_fragments[i], copy.read(src_fragments[i])); | |
52 } | |
53 continue; | |
54 } | |
55 // All other moves. | |
56 write(dst, copy.read(src)); | |
22 } | 57 } |
23 } | 58 } |
24 | 59 |
25 bool operator==(const InterpreterState& other) const { | 60 bool operator==(const InterpreterState& other) const { |
26 return values_ == other.values_; | 61 return values_ == other.values_; |
27 } | 62 } |
28 | 63 |
29 bool operator!=(const InterpreterState& other) const { | |
30 return values_ != other.values_; | |
31 } | |
32 | |
33 private: | 64 private: |
65 // struct for mapping operands to a unique value, that makes it easier to | |
66 // detect illegal parallel moves, and to evaluate moves for equivalence. This | |
67 // is a one way transformation. All general register and slot operands are | |
68 // mapped to the default representation. FP registers and slots are mapped to | |
69 // float64 except on architectures with non-simple FP register aliasing, where | |
70 // the actual representation is used. | |
34 struct Key { | 71 struct Key { |
35 bool is_constant; | 72 bool is_constant; |
36 MachineRepresentation rep; | 73 MachineRepresentation rep; |
37 LocationOperand::LocationKind kind; | 74 LocationOperand::LocationKind kind; |
38 int index; | 75 int index; |
39 | 76 |
40 bool operator<(const Key& other) const { | 77 bool operator<(const Key& other) const { |
41 if (this->is_constant != other.is_constant) { | 78 if (this->is_constant != other.is_constant) { |
42 return this->is_constant; | 79 return this->is_constant; |
43 } | 80 } |
44 if (this->rep != other.rep) { | 81 if (this->rep != other.rep) { |
45 return static_cast<int>(this->rep) < static_cast<int>(other.rep); | 82 return this->rep < other.rep; |
46 } | 83 } |
47 if (this->kind != other.kind) { | 84 if (this->kind != other.kind) { |
48 return this->kind < other.kind; | 85 return this->kind < other.kind; |
49 } | 86 } |
50 return this->index < other.index; | 87 return this->index < other.index; |
51 } | 88 } |
52 | 89 |
53 bool operator==(const Key& other) const { | 90 bool operator==(const Key& other) const { |
54 return this->is_constant == other.is_constant && this->rep == other.rep && | 91 return this->is_constant == other.is_constant && this->rep == other.rep && |
55 this->kind == other.kind && this->index == other.index; | 92 this->kind == other.kind && this->index == other.index; |
56 } | 93 } |
57 }; | 94 }; |
58 | 95 |
59 // Internally, the state is a normalized permutation of (kind,index) pairs. | 96 // Internally, the state is a normalized permutation of Value pairs. |
60 typedef Key Value; | 97 typedef Key Value; |
61 typedef std::map<Key, Value> OperandMap; | 98 typedef std::map<Key, Value> OperandMap; |
62 | 99 |
63 Value read(const InstructionOperand& op) const { | 100 Value read(const InstructionOperand& op) const { |
64 OperandMap::const_iterator it = values_.find(KeyFor(op)); | 101 OperandMap::const_iterator it = values_.find(KeyFor(op)); |
65 return (it == values_.end()) ? ValueFor(op) : it->second; | 102 return (it == values_.end()) ? ValueFor(op) : it->second; |
66 } | 103 } |
67 | 104 |
68 void write(const InstructionOperand& op, Value v) { | 105 void write(const InstructionOperand& dst, Value v) { |
69 if (v == ValueFor(op)) { | 106 if (v == ValueFor(dst)) { |
70 values_.erase(KeyFor(op)); | 107 values_.erase(KeyFor(dst)); |
71 } else { | 108 } else { |
72 values_[KeyFor(op)] = v; | 109 values_[KeyFor(dst)] = v; |
73 } | 110 } |
74 } | 111 } |
75 | 112 |
76 static Key KeyFor(const InstructionOperand& op) { | 113 static Key KeyFor(const InstructionOperand& op) { |
77 bool is_constant = op.IsConstant(); | 114 bool is_constant = op.IsConstant(); |
78 MachineRepresentation rep = | 115 MachineRepresentation rep = |
79 v8::internal::compiler::InstructionSequence::DefaultRepresentation(); | 116 v8::internal::compiler::InstructionSequence::DefaultRepresentation(); |
80 LocationOperand::LocationKind kind; | 117 LocationOperand::LocationKind kind; |
81 int index; | 118 int index; |
82 if (!is_constant) { | 119 if (!is_constant) { |
83 const LocationOperand& loc_op = LocationOperand::cast(op); | 120 const LocationOperand& loc_op = LocationOperand::cast(op); |
121 // Canonicalize FP location operand representations to kFloat64. | |
122 if (IsFloatingPoint(loc_op.representation())) { | |
123 rep = MachineRepresentation::kFloat64; | |
124 } | |
84 if (loc_op.IsAnyRegister()) { | 125 if (loc_op.IsAnyRegister()) { |
85 if (loc_op.IsFPRegister()) { | |
86 rep = MachineRepresentation::kFloat64; | |
87 } | |
88 index = loc_op.register_code(); | 126 index = loc_op.register_code(); |
89 } else { | 127 } else { |
90 index = loc_op.index(); | 128 index = loc_op.index(); |
91 } | 129 } |
92 kind = loc_op.location_kind(); | 130 kind = loc_op.location_kind(); |
93 } else { | 131 } else { |
94 index = ConstantOperand::cast(op).virtual_register(); | 132 index = ConstantOperand::cast(op).virtual_register(); |
95 kind = LocationOperand::REGISTER; | 133 kind = LocationOperand::REGISTER; |
96 } | 134 } |
97 Key key = {is_constant, rep, kind, index}; | 135 Key key = {is_constant, rep, kind, index}; |
(...skipping 10 matching lines...) Expand all Loading... | |
108 } | 146 } |
109 | 147 |
110 friend std::ostream& operator<<(std::ostream& os, | 148 friend std::ostream& operator<<(std::ostream& os, |
111 const InterpreterState& is) { | 149 const InterpreterState& is) { |
112 for (OperandMap::const_iterator it = is.values_.begin(); | 150 for (OperandMap::const_iterator it = is.values_.begin(); |
113 it != is.values_.end(); ++it) { | 151 it != is.values_.end(); ++it) { |
114 if (it != is.values_.begin()) os << " "; | 152 if (it != is.values_.begin()) os << " "; |
115 InstructionOperand source = FromKey(it->second); | 153 InstructionOperand source = FromKey(it->second); |
116 InstructionOperand destination = FromKey(it->first); | 154 InstructionOperand destination = FromKey(it->first); |
117 MoveOperands mo(source, destination); | 155 MoveOperands mo(source, destination); |
118 PrintableMoveOperands pmo = {RegisterConfiguration::Turbofan(), &mo}; | 156 PrintableMoveOperands pmo = {GetRegConfig(), &mo}; |
119 os << pmo; | 157 os << pmo; |
120 } | 158 } |
121 return os; | 159 return os; |
122 } | 160 } |
123 | 161 |
124 OperandMap values_; | 162 OperandMap values_; |
125 }; | 163 }; |
126 | 164 |
127 | |
128 // An abstract interpreter for moves, swaps and parallel moves. | 165 // An abstract interpreter for moves, swaps and parallel moves. |
129 class MoveInterpreter : public GapResolver::Assembler { | 166 class MoveInterpreter : public GapResolver::Assembler { |
130 public: | 167 public: |
131 explicit MoveInterpreter(Zone* zone) : zone_(zone) {} | 168 explicit MoveInterpreter(Zone* zone) : zone_(zone) {} |
132 | 169 |
133 void AssembleMove(InstructionOperand* source, | 170 void AssembleMove(InstructionOperand* source, |
134 InstructionOperand* destination) override { | 171 InstructionOperand* destination) override { |
135 ParallelMove* moves = new (zone_) ParallelMove(zone_); | 172 ParallelMove* moves = new (zone_) ParallelMove(zone_); |
136 moves->AddMove(*source, *destination); | 173 moves->AddMove(*source, *destination); |
137 state_.ExecuteInParallel(moves); | 174 state_.ExecuteInParallel(moves); |
(...skipping 16 matching lines...) Expand all Loading... | |
154 private: | 191 private: |
155 Zone* const zone_; | 192 Zone* const zone_; |
156 InterpreterState state_; | 193 InterpreterState state_; |
157 }; | 194 }; |
158 | 195 |
159 | 196 |
160 class ParallelMoveCreator : public HandleAndZoneScope { | 197 class ParallelMoveCreator : public HandleAndZoneScope { |
161 public: | 198 public: |
162 ParallelMoveCreator() : rng_(CcTest::random_number_generator()) {} | 199 ParallelMoveCreator() : rng_(CcTest::random_number_generator()) {} |
163 | 200 |
201 // Creates a ParallelMove with 'size' random MoveOperands. Note that illegal | |
202 // moves will be rejected, so the actual number of MoveOperands may be less. | |
164 ParallelMove* Create(int size) { | 203 ParallelMove* Create(int size) { |
165 ParallelMove* parallel_move = new (main_zone()) ParallelMove(main_zone()); | 204 ParallelMove* parallel_move = new (main_zone()) ParallelMove(main_zone()); |
166 std::set<InstructionOperand, CompareOperandModuloType> seen; | 205 // Valid ParallelMoves can't have interfering destination ops. |
206 std::set<InstructionOperand, CompareOperandModuloType> destinations; | |
207 // Valid ParallelMoves can't have interfering source ops of different reps. | |
208 std::map<InstructionOperand, MachineRepresentation, | |
209 CompareOperandModuloType> | |
210 sources; | |
167 for (int i = 0; i < size; ++i) { | 211 for (int i = 0; i < size; ++i) { |
168 MachineRepresentation rep = RandomRepresentation(); | 212 MachineRepresentation rep = RandomRepresentation(); |
169 MoveOperands mo(CreateRandomOperand(true, rep), | 213 MoveOperands mo(CreateRandomOperand(true, rep), |
170 CreateRandomOperand(false, rep)); | 214 CreateRandomOperand(false, rep)); |
171 if (!mo.IsRedundant() && seen.find(mo.destination()) == seen.end()) { | 215 if (mo.IsRedundant()) continue; |
216 | |
217 const InstructionOperand& dst = mo.destination(); | |
218 bool reject = false; | |
219 // On architectures where FP register aliasing is non-simple, update the | |
220 // destinations set with the float equivalents of the operand and check | |
221 // that all destinations are unique and do not alias each other. | |
222 if (!kSimpleFPAliasing && IsFPLocation(mo.destination())) { | |
223 std::vector<InstructionOperand> fragments; | |
224 GetCanonicalOperands(dst, &fragments); | |
225 CHECK(!fragments.empty()); | |
226 for (size_t i = 0; i < fragments.size(); ++i) { | |
227 if (destinations.find(fragments[i]) == destinations.end()) { | |
228 destinations.insert(fragments[i]); | |
229 } else { | |
230 reject = true; | |
231 break; | |
232 } | |
233 } | |
234 // Update the sources map, and check that no FP source has multiple | |
235 // representations. | |
236 const InstructionOperand& src = mo.source(); | |
237 if (src.IsFPRegister()) { | |
238 std::vector<InstructionOperand> fragments; | |
239 MachineRepresentation src_rep = | |
240 LocationOperand::cast(src).representation(); | |
241 GetCanonicalOperands(src, &fragments); | |
242 CHECK(!fragments.empty()); | |
243 for (size_t i = 0; i < fragments.size(); ++i) { | |
244 auto find_it = sources.find(fragments[i]); | |
245 if (find_it != sources.end() && find_it->second != src_rep) { | |
246 reject = true; | |
247 break; | |
248 } | |
249 sources.insert(std::make_pair(fragments[i], src_rep)); | |
250 } | |
251 } | |
252 } else { | |
253 if (destinations.find(dst) == destinations.end()) { | |
254 destinations.insert(dst); | |
255 } else { | |
256 reject = true; | |
257 } | |
258 } | |
259 | |
260 if (!reject) { | |
172 parallel_move->AddMove(mo.source(), mo.destination()); | 261 parallel_move->AddMove(mo.source(), mo.destination()); |
173 seen.insert(mo.destination()); | |
174 } | 262 } |
175 } | 263 } |
176 return parallel_move; | 264 return parallel_move; |
177 } | 265 } |
178 | 266 |
267 // Creates a ParallelMove from a list of operand pairs. Even operands are | |
268 // destinations, odd ones are sources. | |
269 ParallelMove* Create(const std::vector<InstructionOperand>& operand_pairs) { | |
270 ParallelMove* parallel_move = new (main_zone()) ParallelMove(main_zone()); | |
271 for (size_t i = 0; i < operand_pairs.size(); i += 2) { | |
272 const InstructionOperand& dst = operand_pairs[i]; | |
273 const InstructionOperand& src = operand_pairs[i + 1]; | |
274 parallel_move->AddMove(src, dst); | |
275 } | |
276 return parallel_move; | |
277 } | |
278 | |
179 private: | 279 private: |
180 MachineRepresentation RandomRepresentation() { | 280 MachineRepresentation RandomRepresentation() { |
181 int index = rng_->NextInt(5); | 281 int index = rng_->NextInt(6); |
182 switch (index) { | 282 switch (index) { |
183 case 0: | 283 case 0: |
184 return MachineRepresentation::kWord32; | 284 return MachineRepresentation::kWord32; |
185 case 1: | 285 case 1: |
186 return MachineRepresentation::kWord64; | 286 return MachineRepresentation::kWord64; |
187 case 2: | 287 case 2: |
188 return MachineRepresentation::kFloat32; | 288 return MachineRepresentation::kFloat32; |
189 case 3: | 289 case 3: |
190 return MachineRepresentation::kFloat64; | 290 return MachineRepresentation::kFloat64; |
191 case 4: | 291 case 4: |
292 return MachineRepresentation::kSimd128; | |
293 case 5: | |
192 return MachineRepresentation::kTagged; | 294 return MachineRepresentation::kTagged; |
193 } | 295 } |
194 UNREACHABLE(); | 296 UNREACHABLE(); |
195 return MachineRepresentation::kNone; | 297 return MachineRepresentation::kNone; |
196 } | 298 } |
197 | 299 |
300 const int kMaxIndex = 7; | |
301 const int kMaxIndices = kMaxIndex + 1; | |
302 | |
303 // Non-FP slots shouldn't overlap FP slots. | |
304 // FP slots with different representations shouldn't overlap. | |
305 int GetValidSlotIndex(MachineRepresentation rep, int index) { | |
306 DCHECK_GE(kMaxIndex, index); | |
307 // The first group of slots are for non-FP values. | |
308 if (!IsFloatingPoint(rep)) return index; | |
309 // The next group are for float values. | |
310 int base = kMaxIndices; | |
311 if (rep == MachineRepresentation::kFloat32) return base + index; | |
312 // Double values. | |
313 base += kMaxIndices; | |
314 if (rep == MachineRepresentation::kFloat64) return base + index * 2; | |
315 // SIMD values | |
316 base += kMaxIndices * 2; | |
317 CHECK_EQ(MachineRepresentation::kSimd128, rep); | |
318 return base + index * 4; | |
319 } | |
320 | |
198 InstructionOperand CreateRandomOperand(bool is_source, | 321 InstructionOperand CreateRandomOperand(bool is_source, |
199 MachineRepresentation rep) { | 322 MachineRepresentation rep) { |
200 auto conf = RegisterConfiguration::Turbofan(); | 323 auto conf = RegisterConfiguration::Turbofan(); |
201 auto GetRegisterCode = [&conf](MachineRepresentation rep, int index) { | 324 auto GetValidRegisterCode = [&conf](MachineRepresentation rep, int index) { |
202 switch (rep) { | 325 switch (rep) { |
203 case MachineRepresentation::kFloat32: | 326 case MachineRepresentation::kFloat32: |
204 #if V8_TARGET_ARCH_ARM | |
205 // Only even number float registers are used on Arm. | |
206 // TODO(bbudge) Eliminate this when FP register aliasing works. | |
207 return conf->RegisterConfiguration::GetAllocatableDoubleCode(index) * | |
208 2; | |
209 #endif | |
210 // Fall through on non-Arm targets. | |
211 case MachineRepresentation::kFloat64: | 327 case MachineRepresentation::kFloat64: |
328 case MachineRepresentation::kSimd128: | |
212 return conf->RegisterConfiguration::GetAllocatableDoubleCode(index); | 329 return conf->RegisterConfiguration::GetAllocatableDoubleCode(index); |
213 | |
214 default: | 330 default: |
215 return conf->RegisterConfiguration::GetAllocatableGeneralCode(index); | 331 return conf->RegisterConfiguration::GetAllocatableGeneralCode(index); |
216 } | 332 } |
217 UNREACHABLE(); | 333 UNREACHABLE(); |
218 return static_cast<int>(Register::kCode_no_reg); | 334 return static_cast<int>(Register::kCode_no_reg); |
219 }; | 335 }; |
220 int index = rng_->NextInt(7); | 336 int index = rng_->NextInt(kMaxIndex); |
221 // destination can't be Constant. | 337 // destination can't be Constant. |
222 switch (rng_->NextInt(is_source ? 5 : 4)) { | 338 switch (rng_->NextInt(is_source ? 5 : 4)) { |
223 case 0: | 339 case 0: |
224 return AllocatedOperand(LocationOperand::STACK_SLOT, rep, index); | 340 return AllocatedOperand(LocationOperand::STACK_SLOT, rep, |
341 GetValidSlotIndex(rep, index)); | |
225 case 1: | 342 case 1: |
226 return AllocatedOperand(LocationOperand::REGISTER, rep, index); | 343 return AllocatedOperand(LocationOperand::REGISTER, rep, |
344 GetValidRegisterCode(rep, index)); | |
227 case 2: | 345 case 2: |
228 return ExplicitOperand(LocationOperand::REGISTER, rep, | 346 return ExplicitOperand(LocationOperand::REGISTER, rep, |
229 GetRegisterCode(rep, 1)); | 347 GetValidRegisterCode(rep, 1)); |
230 case 3: | 348 case 3: |
231 return ExplicitOperand(LocationOperand::STACK_SLOT, rep, | 349 return ExplicitOperand(LocationOperand::STACK_SLOT, rep, |
232 GetRegisterCode(rep, index)); | 350 GetValidSlotIndex(rep, index)); |
233 case 4: | 351 case 4: |
234 return ConstantOperand(index); | 352 return ConstantOperand(index); |
235 } | 353 } |
236 UNREACHABLE(); | 354 UNREACHABLE(); |
237 return InstructionOperand(); | 355 return InstructionOperand(); |
238 } | 356 } |
239 | 357 |
240 private: | 358 private: |
241 v8::base::RandomNumberGenerator* rng_; | 359 v8::base::RandomNumberGenerator* rng_; |
242 }; | 360 }; |
243 | 361 |
362 void RunTest(ParallelMove* pm, Zone* zone) { | |
363 // Note: The gap resolver modifies the ParallelMove, so interpret first. | |
364 MoveInterpreter mi1(zone); | |
365 mi1.AssembleParallelMove(pm); | |
366 | |
367 MoveInterpreter mi2(zone); | |
368 GapResolver resolver(&mi2); | |
369 resolver.Resolve(pm); | |
370 | |
371 CHECK_EQ(mi1.state(), mi2.state()); | |
372 } | |
244 | 373 |
245 TEST(FuzzResolver) { | 374 TEST(FuzzResolver) { |
246 ParallelMoveCreator pmc; | 375 ParallelMoveCreator pmc; |
247 for (int size = 0; size < 20; ++size) { | 376 for (int size = 0; size < 80; ++size) { |
248 for (int repeat = 0; repeat < 50; ++repeat) { | 377 for (int repeat = 0; repeat < 50; ++repeat) { |
249 ParallelMove* pm = pmc.Create(size); | 378 RunTest(pmc.Create(size), pmc.main_zone()); |
250 | |
251 // Note: The gap resolver modifies the ParallelMove, so interpret first. | |
252 MoveInterpreter mi1(pmc.main_zone()); | |
253 mi1.AssembleParallelMove(pm); | |
254 | |
255 MoveInterpreter mi2(pmc.main_zone()); | |
256 GapResolver resolver(&mi2); | |
257 resolver.Resolve(pm); | |
258 | |
259 CHECK_EQ(mi1.state(), mi2.state()); | |
260 } | 379 } |
261 } | 380 } |
262 } | 381 } |
263 | 382 |
264 } // namespace compiler | 383 } // namespace compiler |
265 } // namespace internal | 384 } // namespace internal |
266 } // namespace v8 | 385 } // namespace v8 |
OLD | NEW |