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