Chromium Code Reviews| 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 #ifndef V8_REGISTER_ALLOCATOR_H_ | 5 #ifndef V8_REGISTER_ALLOCATOR_H_ |
| 6 #define V8_REGISTER_ALLOCATOR_H_ | 6 #define V8_REGISTER_ALLOCATOR_H_ |
| 7 | 7 |
| 8 #include "src/allocation.h" | 8 #include "src/allocation.h" |
| 9 #include "src/compiler/instruction.h" | 9 #include "src/compiler/instruction.h" |
| 10 #include "src/compiler/zone-pool.h" | |
| 11 #include "src/macro-assembler.h" | 10 #include "src/macro-assembler.h" |
| 12 #include "src/zone.h" | 11 #include "src/zone.h" |
| 13 | 12 |
| 14 namespace v8 { | 13 namespace v8 { |
| 15 namespace internal { | 14 namespace internal { |
| 16 | 15 |
| 17 // Forward declarations. | 16 // Forward declarations. |
| 18 class BitVector; | 17 class BitVector; |
| 19 class InstructionOperand; | 18 class InstructionOperand; |
| 20 class UnallocatedOperand; | 19 class UnallocatedOperand; |
| 21 class ParallelMove; | 20 class ParallelMove; |
| 22 class PointerMap; | 21 class PointerMap; |
| 23 | 22 |
| 24 namespace compiler { | 23 namespace compiler { |
| 25 | 24 |
| 26 class PipelineStatistics; | 25 class PipelineStatistics; |
| 27 | 26 |
| 28 enum RegisterKind { | 27 enum RegisterKind { |
| 29 UNALLOCATED_REGISTERS, | 28 UNALLOCATED_REGISTERS, |
| 30 GENERAL_REGISTERS, | 29 GENERAL_REGISTERS, |
| 31 DOUBLE_REGISTERS | 30 DOUBLE_REGISTERS |
| 32 }; | 31 }; |
| 33 | 32 |
| 34 | 33 |
| 35 // This class represents a single point of a InstructionOperand's lifetime. For | 34 // This class represents a single point of a InstructionOperand's lifetime. For |
| 36 // each instruction there are exactly two lifetime positions: the beginning and | 35 // each instruction there are exactly two lifetime positions: the beginning and |
| 37 // the end of the instruction. Lifetime positions for different instructions are | 36 // the end of the instruction. Lifetime positions for different instructions are |
| 38 // disjoint. | 37 // disjoint. |
| 39 class LifetimePosition { | 38 class LifetimePosition FINAL { |
| 40 public: | 39 public: |
| 41 // Return the lifetime position that corresponds to the beginning of | 40 // Return the lifetime position that corresponds to the beginning of |
| 42 // the instruction with the given index. | 41 // the instruction with the given index. |
| 43 static LifetimePosition FromInstructionIndex(int index) { | 42 static LifetimePosition FromInstructionIndex(int index) { |
| 44 return LifetimePosition(index * kStep); | 43 return LifetimePosition(index * kStep); |
| 45 } | 44 } |
| 46 | 45 |
| 47 // Returns a numeric representation of this lifetime position. | 46 // Returns a numeric representation of this lifetime position. |
| 48 int Value() const { return value_; } | 47 int Value() const { return value_; } |
| 49 | 48 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 108 // Code relies on kStep being a power of two. | 107 // Code relies on kStep being a power of two. |
| 109 STATIC_ASSERT(IS_POWER_OF_TWO(kStep)); | 108 STATIC_ASSERT(IS_POWER_OF_TWO(kStep)); |
| 110 | 109 |
| 111 explicit LifetimePosition(int value) : value_(value) {} | 110 explicit LifetimePosition(int value) : value_(value) {} |
| 112 | 111 |
| 113 int value_; | 112 int value_; |
| 114 }; | 113 }; |
| 115 | 114 |
| 116 | 115 |
| 117 // Representation of the non-empty interval [start,end[. | 116 // Representation of the non-empty interval [start,end[. |
| 118 class UseInterval : public ZoneObject { | 117 class UseInterval FINAL : public ZoneObject { |
| 119 public: | 118 public: |
| 120 UseInterval(LifetimePosition start, LifetimePosition end) | 119 UseInterval(LifetimePosition start, LifetimePosition end) |
| 121 : start_(start), end_(end), next_(NULL) { | 120 : start_(start), end_(end), next_(NULL) { |
| 122 DCHECK(start.Value() < end.Value()); | 121 DCHECK(start.Value() < end.Value()); |
| 123 } | 122 } |
| 124 | 123 |
| 125 LifetimePosition start() const { return start_; } | 124 LifetimePosition start() const { return start_; } |
| 126 LifetimePosition end() const { return end_; } | 125 LifetimePosition end() const { return end_; } |
| 127 UseInterval* next() const { return next_; } | 126 UseInterval* next() const { return next_; } |
| 128 | 127 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 141 bool Contains(LifetimePosition point) const { | 140 bool Contains(LifetimePosition point) const { |
| 142 return start_.Value() <= point.Value() && point.Value() < end_.Value(); | 141 return start_.Value() <= point.Value() && point.Value() < end_.Value(); |
| 143 } | 142 } |
| 144 | 143 |
| 145 void set_start(LifetimePosition start) { start_ = start; } | 144 void set_start(LifetimePosition start) { start_ = start; } |
| 146 void set_next(UseInterval* next) { next_ = next; } | 145 void set_next(UseInterval* next) { next_ = next; } |
| 147 | 146 |
| 148 LifetimePosition start_; | 147 LifetimePosition start_; |
| 149 LifetimePosition end_; | 148 LifetimePosition end_; |
| 150 UseInterval* next_; | 149 UseInterval* next_; |
| 150 | |
| 151 private: | |
| 152 DISALLOW_COPY_AND_ASSIGN(UseInterval); | |
| 151 }; | 153 }; |
| 152 | 154 |
| 155 | |
| 153 // Representation of a use position. | 156 // Representation of a use position. |
| 154 class UsePosition : public ZoneObject { | 157 class UsePosition FINAL : public ZoneObject { |
| 155 public: | 158 public: |
| 156 UsePosition(LifetimePosition pos, InstructionOperand* operand, | 159 UsePosition(LifetimePosition pos, InstructionOperand* operand, |
| 157 InstructionOperand* hint); | 160 InstructionOperand* hint); |
| 158 | 161 |
| 159 InstructionOperand* operand() const { return operand_; } | 162 InstructionOperand* operand() const { return operand_; } |
| 160 bool HasOperand() const { return operand_ != NULL; } | 163 bool HasOperand() const { return operand_ != NULL; } |
| 161 | 164 |
| 162 InstructionOperand* hint() const { return hint_; } | 165 InstructionOperand* hint() const { return hint_; } |
| 163 bool HasHint() const; | 166 bool HasHint() const; |
| 164 bool RequiresRegister() const; | 167 bool RequiresRegister() const; |
| 165 bool RegisterIsBeneficial() const; | 168 bool RegisterIsBeneficial() const; |
| 166 | 169 |
| 167 LifetimePosition pos() const { return pos_; } | 170 LifetimePosition pos() const { return pos_; } |
| 168 UsePosition* next() const { return next_; } | 171 UsePosition* next() const { return next_; } |
| 169 | 172 |
| 170 void set_next(UsePosition* next) { next_ = next; } | 173 void set_next(UsePosition* next) { next_ = next; } |
| 171 | 174 |
| 172 InstructionOperand* const operand_; | 175 InstructionOperand* const operand_; |
| 173 InstructionOperand* const hint_; | 176 InstructionOperand* const hint_; |
| 174 LifetimePosition const pos_; | 177 LifetimePosition const pos_; |
| 175 UsePosition* next_; | 178 UsePosition* next_; |
| 176 bool requires_reg_; | 179 bool requires_reg_ : 1; |
|
Jarin
2014/10/29 12:16:51
I do not think the bit field makes any difference.
| |
| 177 bool register_beneficial_; | 180 bool register_beneficial_ : 1; |
| 181 | |
| 182 private: | |
| 183 DISALLOW_COPY_AND_ASSIGN(UsePosition); | |
| 178 }; | 184 }; |
| 179 | 185 |
| 186 | |
| 180 // Representation of SSA values' live ranges as a collection of (continuous) | 187 // Representation of SSA values' live ranges as a collection of (continuous) |
| 181 // intervals over the instruction ordering. | 188 // intervals over the instruction ordering. |
| 182 class LiveRange : public ZoneObject { | 189 class LiveRange FINAL : public ZoneObject { |
| 183 public: | 190 public: |
| 184 static const int kInvalidAssignment = 0x7fffffff; | 191 static const int kInvalidAssignment = 0x7fffffff; |
| 185 | 192 |
| 186 LiveRange(int id, Zone* zone); | 193 LiveRange(int id, Zone* zone); |
| 187 | 194 |
| 188 UseInterval* first_interval() const { return first_interval_; } | 195 UseInterval* first_interval() const { return first_interval_; } |
| 189 UsePosition* first_pos() const { return first_pos_; } | 196 UsePosition* first_pos() const { return first_pos_; } |
| 190 LiveRange* parent() const { return parent_; } | 197 LiveRange* parent() const { return parent_; } |
| 191 LiveRange* TopLevel() { return (parent_ == NULL) ? this : parent_; } | 198 LiveRange* TopLevel() { return (parent_ == NULL) ? this : parent_; } |
| 192 LiveRange* next() const { return next_; } | 199 LiveRange* next() const { return next_; } |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 289 void Verify() const; | 296 void Verify() const; |
| 290 #endif | 297 #endif |
| 291 | 298 |
| 292 private: | 299 private: |
| 293 void ConvertOperands(Zone* zone); | 300 void ConvertOperands(Zone* zone); |
| 294 UseInterval* FirstSearchIntervalForPosition(LifetimePosition position) const; | 301 UseInterval* FirstSearchIntervalForPosition(LifetimePosition position) const; |
| 295 void AdvanceLastProcessedMarker(UseInterval* to_start_of, | 302 void AdvanceLastProcessedMarker(UseInterval* to_start_of, |
| 296 LifetimePosition but_not_past) const; | 303 LifetimePosition but_not_past) const; |
| 297 | 304 |
| 298 int id_; | 305 int id_; |
| 299 bool spilled_; | |
| 300 bool is_phi_; | |
| 301 bool is_non_loop_phi_; | |
| 302 RegisterKind kind_; | |
| 303 int assigned_register_; | 306 int assigned_register_; |
| 307 bool spilled_ : 1; | |
| 308 bool is_phi_ : 1; | |
| 309 bool is_non_loop_phi_ : 1; | |
| 310 RegisterKind kind_ : 2; | |
|
Jarin
2014/10/29 12:16:51
Does the bitfield make any difference?
If yes, th
| |
| 304 UseInterval* last_interval_; | 311 UseInterval* last_interval_; |
| 305 UseInterval* first_interval_; | 312 UseInterval* first_interval_; |
| 306 UsePosition* first_pos_; | 313 UsePosition* first_pos_; |
| 307 LiveRange* parent_; | 314 LiveRange* parent_; |
| 308 LiveRange* next_; | 315 LiveRange* next_; |
| 309 // This is used as a cache, it doesn't affect correctness. | 316 // This is used as a cache, it doesn't affect correctness. |
| 310 mutable UseInterval* current_interval_; | 317 mutable UseInterval* current_interval_; |
| 311 UsePosition* last_processed_use_; | 318 UsePosition* last_processed_use_; |
| 312 // This is used as a cache, it's invalid outside of BuildLiveRanges. | 319 // This is used as a cache, it's invalid outside of BuildLiveRanges. |
| 313 InstructionOperand* current_hint_operand_; | 320 InstructionOperand* current_hint_operand_; |
| 314 InstructionOperand* spill_operand_; | 321 InstructionOperand* spill_operand_; |
| 315 int spill_start_index_; | 322 int spill_start_index_; |
| 316 | 323 |
| 317 friend class RegisterAllocator; // Assigns to kind_. | 324 friend class RegisterAllocator; // Assigns to kind_. |
| 325 | |
| 326 DISALLOW_COPY_AND_ASSIGN(LiveRange); | |
| 318 }; | 327 }; |
| 319 | 328 |
| 320 | 329 |
| 321 class RegisterAllocator BASE_EMBEDDED { | 330 class RegisterAllocator BASE_EMBEDDED { |
| 322 public: | 331 public: |
| 323 // TODO(dcarney): remove info | |
| 324 explicit RegisterAllocator(Zone* local_zone, Frame* frame, | 332 explicit RegisterAllocator(Zone* local_zone, Frame* frame, |
| 325 CompilationInfo* info, InstructionSequence* code); | 333 InstructionSequence* code, |
| 326 | 334 const char* debug_name = nullptr); |
| 327 static void TraceAlloc(const char* msg, ...); | |
| 328 | |
| 329 // Checks whether the value of a given virtual register is a reference. | |
| 330 // TODO(titzer): rename this to IsReference. | |
| 331 bool HasTaggedValue(int virtual_register) const; | |
| 332 | |
| 333 // Returns the register kind required by the given virtual register. | |
| 334 RegisterKind RequiredRegisterKind(int virtual_register) const; | |
| 335 | 335 |
| 336 bool Allocate(PipelineStatistics* stats = NULL); | 336 bool Allocate(PipelineStatistics* stats = NULL); |
| 337 bool AllocationOk() { return allocation_ok_; } | |
| 338 BitVector* assigned_registers() { return assigned_registers_; } | |
| 339 BitVector* assigned_double_registers() { return assigned_double_registers_; } | |
| 337 | 340 |
| 338 const ZoneList<LiveRange*>* live_ranges() const { return &live_ranges_; } | 341 const ZoneList<LiveRange*>* live_ranges() const { return &live_ranges_; } |
| 339 const Vector<LiveRange*>* fixed_live_ranges() const { | 342 const Vector<LiveRange*>* fixed_live_ranges() const { |
| 340 return &fixed_live_ranges_; | 343 return &fixed_live_ranges_; |
| 341 } | 344 } |
| 342 const Vector<LiveRange*>* fixed_double_live_ranges() const { | 345 const Vector<LiveRange*>* fixed_double_live_ranges() const { |
| 343 return &fixed_double_live_ranges_; | 346 return &fixed_double_live_ranges_; |
| 344 } | 347 } |
| 348 InstructionSequence* code() const { return code_; } | |
| 345 | 349 |
| 346 CompilationInfo* info() const { return info_; } | 350 private: |
| 347 inline InstructionSequence* code() const { return code_; } | |
| 348 | |
| 349 // This zone is for datastructures only needed during register allocation. | |
| 350 inline Zone* zone() const { return zone_; } | |
| 351 | |
| 352 // This zone is for InstructionOperands and moves that live beyond register | |
| 353 // allocation. | |
| 354 inline Zone* code_zone() const { return code()->zone(); } | |
| 355 | |
| 356 int GetVirtualRegister() { | 351 int GetVirtualRegister() { |
| 357 int vreg = code()->NextVirtualRegister(); | 352 int vreg = code()->NextVirtualRegister(); |
| 358 if (vreg >= UnallocatedOperand::kMaxVirtualRegisters) { | 353 if (vreg >= UnallocatedOperand::kMaxVirtualRegisters) { |
| 359 allocation_ok_ = false; | 354 allocation_ok_ = false; |
| 360 // Maintain the invariant that we return something below the maximum. | 355 // Maintain the invariant that we return something below the maximum. |
| 361 return 0; | 356 return 0; |
| 362 } | 357 } |
| 363 return vreg; | 358 return vreg; |
| 364 } | 359 } |
| 365 | 360 |
| 366 bool AllocationOk() { return allocation_ok_; } | 361 // Checks whether the value of a given virtual register is a reference. |
| 362 // TODO(titzer): rename this to IsReference. | |
| 363 bool HasTaggedValue(int virtual_register) const; | |
| 364 | |
| 365 // Returns the register kind required by the given virtual register. | |
| 366 RegisterKind RequiredRegisterKind(int virtual_register) const; | |
| 367 | |
| 368 // This zone is for datastructures only needed during register allocation. | |
| 369 Zone* zone() const { return zone_; } | |
| 370 | |
| 371 // This zone is for InstructionOperands and moves that live beyond register | |
| 372 // allocation. | |
| 373 Zone* code_zone() const { return code()->zone(); } | |
| 367 | 374 |
| 368 #ifdef DEBUG | 375 #ifdef DEBUG |
| 369 void Verify() const; | 376 void Verify() const; |
| 370 #endif | 377 #endif |
| 371 | 378 |
| 372 BitVector* assigned_registers() { return assigned_registers_; } | |
| 373 BitVector* assigned_double_registers() { return assigned_double_registers_; } | |
| 374 | |
| 375 private: | |
| 376 void MeetRegisterConstraints(); | 379 void MeetRegisterConstraints(); |
| 377 void ResolvePhis(); | 380 void ResolvePhis(); |
| 378 void BuildLiveRanges(); | 381 void BuildLiveRanges(); |
| 379 void AllocateGeneralRegisters(); | 382 void AllocateGeneralRegisters(); |
| 380 void AllocateDoubleRegisters(); | 383 void AllocateDoubleRegisters(); |
| 381 void ConnectRanges(); | 384 void ConnectRanges(); |
| 382 void ResolveControlFlow(); | 385 void ResolveControlFlow(); |
| 383 void PopulatePointerMaps(); // TODO(titzer): rename to PopulateReferenceMaps. | 386 void PopulatePointerMaps(); // TODO(titzer): rename to PopulateReferenceMaps. |
| 384 void AllocateRegisters(); | 387 void AllocateRegisters(); |
| 385 bool CanEagerlyResolveControlFlow(const InstructionBlock* block) const; | 388 bool CanEagerlyResolveControlFlow(const InstructionBlock* block) const; |
| 386 inline bool SafePointsAreInOrder() const; | 389 bool SafePointsAreInOrder() const; |
| 387 | 390 |
| 388 // Liveness analysis support. | 391 // Liveness analysis support. |
| 389 void InitializeLivenessAnalysis(); | 392 void InitializeLivenessAnalysis(); |
| 390 BitVector* ComputeLiveOut(const InstructionBlock* block); | 393 BitVector* ComputeLiveOut(const InstructionBlock* block); |
| 391 void AddInitialIntervals(const InstructionBlock* block, BitVector* live_out); | 394 void AddInitialIntervals(const InstructionBlock* block, BitVector* live_out); |
| 392 bool IsOutputRegisterOf(Instruction* instr, int index); | 395 bool IsOutputRegisterOf(Instruction* instr, int index); |
| 393 bool IsOutputDoubleRegisterOf(Instruction* instr, int index); | 396 bool IsOutputDoubleRegisterOf(Instruction* instr, int index); |
| 394 void ProcessInstructions(const InstructionBlock* block, BitVector* live); | 397 void ProcessInstructions(const InstructionBlock* block, BitVector* live); |
| 395 void MeetRegisterConstraints(const InstructionBlock* block); | 398 void MeetRegisterConstraints(const InstructionBlock* block); |
| 396 void MeetConstraintsBetween(Instruction* first, Instruction* second, | 399 void MeetConstraintsBetween(Instruction* first, Instruction* second, |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 467 LifetimePosition FindOptimalSpillingPos(LiveRange* range, | 470 LifetimePosition FindOptimalSpillingPos(LiveRange* range, |
| 468 LifetimePosition pos); | 471 LifetimePosition pos); |
| 469 | 472 |
| 470 void Spill(LiveRange* range); | 473 void Spill(LiveRange* range); |
| 471 bool IsBlockBoundary(LifetimePosition pos); | 474 bool IsBlockBoundary(LifetimePosition pos); |
| 472 | 475 |
| 473 // Helper methods for resolving control flow. | 476 // Helper methods for resolving control flow. |
| 474 void ResolveControlFlow(LiveRange* range, const InstructionBlock* block, | 477 void ResolveControlFlow(LiveRange* range, const InstructionBlock* block, |
| 475 const InstructionBlock* pred); | 478 const InstructionBlock* pred); |
| 476 | 479 |
| 477 inline void SetLiveRangeAssignedRegister(LiveRange* range, int reg); | 480 void SetLiveRangeAssignedRegister(LiveRange* range, int reg); |
| 478 | 481 |
| 479 // Return parallel move that should be used to connect ranges split at the | 482 // Return parallel move that should be used to connect ranges split at the |
| 480 // given position. | 483 // given position. |
| 481 ParallelMove* GetConnectingParallelMove(LifetimePosition pos); | 484 ParallelMove* GetConnectingParallelMove(LifetimePosition pos); |
| 482 | 485 |
| 483 // Return the block which contains give lifetime position. | 486 // Return the block which contains give lifetime position. |
| 484 const InstructionBlock* GetInstructionBlock(LifetimePosition pos); | 487 const InstructionBlock* GetInstructionBlock(LifetimePosition pos); |
| 485 | 488 |
| 486 // Helper methods for the fixed registers. | 489 // Helper methods for the fixed registers. |
| 487 int RegisterCount() const; | 490 int RegisterCount() const; |
| 488 static int FixedLiveRangeID(int index) { return -index - 1; } | 491 static int FixedLiveRangeID(int index) { return -index - 1; } |
| 489 static int FixedDoubleLiveRangeID(int index); | 492 static int FixedDoubleLiveRangeID(int index); |
| 490 LiveRange* FixedLiveRangeFor(int index); | 493 LiveRange* FixedLiveRangeFor(int index); |
| 491 LiveRange* FixedDoubleLiveRangeFor(int index); | 494 LiveRange* FixedDoubleLiveRangeFor(int index); |
| 492 LiveRange* LiveRangeFor(int index); | 495 LiveRange* LiveRangeFor(int index); |
| 493 GapInstruction* GetLastGap(const InstructionBlock* block); | 496 GapInstruction* GetLastGap(const InstructionBlock* block); |
| 494 | 497 |
| 495 const char* RegisterName(int allocation_index); | 498 const char* RegisterName(int allocation_index); |
| 496 | 499 |
| 497 inline Instruction* InstructionAt(int index) { | 500 Instruction* InstructionAt(int index) { return code()->InstructionAt(index); } |
| 498 return code()->InstructionAt(index); | |
| 499 } | |
| 500 | 501 |
| 501 Frame* frame() const { return frame_; } | 502 Frame* frame() const { return frame_; } |
| 503 const char* debug_name() const { return debug_name_; } | |
| 502 | 504 |
| 503 Zone* const zone_; | 505 Zone* const zone_; |
| 504 Frame* const frame_; | 506 Frame* const frame_; |
| 505 CompilationInfo* const info_; | |
| 506 InstructionSequence* const code_; | 507 InstructionSequence* const code_; |
| 508 const char* const debug_name_; | |
| 507 | 509 |
| 508 // During liveness analysis keep a mapping from block id to live_in sets | 510 // During liveness analysis keep a mapping from block id to live_in sets |
| 509 // for blocks already analyzed. | 511 // for blocks already analyzed. |
| 510 ZoneList<BitVector*> live_in_sets_; | 512 ZoneList<BitVector*> live_in_sets_; |
| 511 | 513 |
| 512 // Liveness analysis results. | 514 // Liveness analysis results. |
| 513 ZoneList<LiveRange*> live_ranges_; | 515 ZoneList<LiveRange*> live_ranges_; |
| 514 | 516 |
| 515 // Lists of live ranges | 517 // Lists of live ranges |
| 516 EmbeddedVector<LiveRange*, Register::kMaxNumAllocatableRegisters> | 518 EmbeddedVector<LiveRange*, Register::kMaxNumAllocatableRegisters> |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 536 #endif | 538 #endif |
| 537 | 539 |
| 538 DISALLOW_COPY_AND_ASSIGN(RegisterAllocator); | 540 DISALLOW_COPY_AND_ASSIGN(RegisterAllocator); |
| 539 }; | 541 }; |
| 540 | 542 |
| 541 } | 543 } |
| 542 } | 544 } |
| 543 } // namespace v8::internal::compiler | 545 } // namespace v8::internal::compiler |
| 544 | 546 |
| 545 #endif // V8_REGISTER_ALLOCATOR_H_ | 547 #endif // V8_REGISTER_ALLOCATOR_H_ |
| OLD | NEW |