| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "vm/globals.h" | 5 #include "vm/globals.h" |
| 6 #if defined(TARGET_ARCH_ARM) | 6 #if defined(TARGET_ARCH_ARM) |
| 7 | 7 |
| 8 #include "vm/assembler.h" | 8 #include "vm/assembler.h" |
| 9 #include "vm/simulator.h" | 9 #include "vm/simulator.h" |
| 10 #include "vm/runtime_entry.h" | 10 #include "vm/runtime_entry.h" |
| (...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 298 static_cast<int32_t>(MOV) << kOpcodeShift | | 298 static_cast<int32_t>(MOV) << kOpcodeShift | |
| 299 static_cast<int32_t>(rd) << kRdShift | | 299 static_cast<int32_t>(rd) << kRdShift | |
| 300 so.encoding() << kShiftRegisterShift | | 300 so.encoding() << kShiftRegisterShift | |
| 301 static_cast<int32_t>(opcode) << kShiftShift | | 301 static_cast<int32_t>(opcode) << kShiftShift | |
| 302 B4 | | 302 B4 | |
| 303 static_cast<int32_t>(rm); | 303 static_cast<int32_t>(rm); |
| 304 Emit(encoding); | 304 Emit(encoding); |
| 305 } | 305 } |
| 306 | 306 |
| 307 | 307 |
| 308 void Assembler::EmitBranch(Condition cond, Label* label, bool link) { | |
| 309 if (label->IsBound()) { | |
| 310 EmitType5(cond, label->Position() - buffer_.Size(), link); | |
| 311 } else { | |
| 312 int position = buffer_.Size(); | |
| 313 // Use the offset field of the branch instruction for linking the sites. | |
| 314 EmitType5(cond, label->position_, link); | |
| 315 label->LinkTo(position); | |
| 316 } | |
| 317 } | |
| 318 | |
| 319 | |
| 320 void Assembler::and_(Register rd, Register rn, ShifterOperand so, | 308 void Assembler::and_(Register rd, Register rn, ShifterOperand so, |
| 321 Condition cond) { | 309 Condition cond) { |
| 322 EmitType01(cond, so.type(), AND, 0, rn, rd, so); | 310 EmitType01(cond, so.type(), AND, 0, rn, rd, so); |
| 323 } | 311 } |
| 324 | 312 |
| 325 | 313 |
| 326 void Assembler::eor(Register rd, Register rn, ShifterOperand so, | 314 void Assembler::eor(Register rd, Register rn, ShifterOperand so, |
| 327 Condition cond) { | 315 Condition cond) { |
| 328 EmitType01(cond, so.type(), EOR, 0, rn, rd, so); | 316 EmitType01(cond, so.type(), EOR, 0, rn, rd, so); |
| 329 } | 317 } |
| (...skipping 1388 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1718 | 1706 |
| 1719 | 1707 |
| 1720 void Assembler::CompareClassId(Register object, | 1708 void Assembler::CompareClassId(Register object, |
| 1721 intptr_t class_id, | 1709 intptr_t class_id, |
| 1722 Register scratch) { | 1710 Register scratch) { |
| 1723 LoadClassId(scratch, object); | 1711 LoadClassId(scratch, object); |
| 1724 CompareImmediate(scratch, class_id); | 1712 CompareImmediate(scratch, class_id); |
| 1725 } | 1713 } |
| 1726 | 1714 |
| 1727 | 1715 |
| 1716 static bool CanEncodeBranchOffset(int32_t offset) { |
| 1717 offset -= Instr::kPCReadOffset; |
| 1718 ASSERT(Utils::IsAligned(offset, 4)); |
| 1719 return Utils::IsInt(Utils::CountOneBits(kBranchOffsetMask), offset); |
| 1720 } |
| 1721 |
| 1722 |
| 1723 int32_t Assembler::EncodeBranchOffset(int32_t offset, int32_t inst) { |
| 1724 // The offset is off by 8 due to the way the ARM CPUs read PC. |
| 1725 offset -= Instr::kPCReadOffset; |
| 1726 ASSERT(Utils::IsAligned(offset, 4)); |
| 1727 ASSERT(Utils::IsInt(Utils::CountOneBits(kBranchOffsetMask), offset)); |
| 1728 |
| 1729 // Properly preserve only the bits supported in the instruction. |
| 1730 offset >>= 2; |
| 1731 offset &= kBranchOffsetMask; |
| 1732 return (inst & ~kBranchOffsetMask) | offset; |
| 1733 } |
| 1734 |
| 1735 |
| 1736 int Assembler::DecodeBranchOffset(int32_t inst) { |
| 1737 // Sign-extend, left-shift by 2, then add 8. |
| 1738 return ((((inst & kBranchOffsetMask) << 8) >> 6) + Instr::kPCReadOffset); |
| 1739 } |
| 1740 |
| 1741 |
| 1742 static int32_t DecodeLoadImmediate(int32_t movt, int32_t movw) { |
| 1743 int32_t offset = 0; |
| 1744 offset |= (movt & 0xf0000) << 12; |
| 1745 offset |= (movt & 0xfff) << 16; |
| 1746 offset |= (movw & 0xf0000) >> 4; |
| 1747 offset |= movw & 0xfff; |
| 1748 return offset; |
| 1749 } |
| 1750 |
| 1751 |
| 1752 class PatchFarBranch : public AssemblerFixup { |
| 1753 public: |
| 1754 PatchFarBranch() {} |
| 1755 |
| 1756 void Process(const MemoryRegion& region, int position) { |
| 1757 const int32_t movw = region.Load<int32_t>(position); |
| 1758 const int32_t movt = region.Load<int32_t>(position + Instr::kInstrSize); |
| 1759 const int32_t bx = region.Load<int32_t>(position + 2 * Instr::kInstrSize); |
| 1760 |
| 1761 if (((movt & 0xfff0f000) == 0xe340c000) && // movt IP, high |
| 1762 ((movw & 0xfff0f000) == 0xe300c000)) { // movw IP, low |
| 1763 const int32_t offset = DecodeLoadImmediate(movt, movw); |
| 1764 const int32_t dest = region.start() + offset; |
| 1765 const uint16_t dest_high = Utils::High16Bits(dest); |
| 1766 const uint16_t dest_low = Utils::Low16Bits(dest); |
| 1767 const int32_t patched_movt = |
| 1768 0xe340c000 | ((dest_high >> 12) << 16) | (dest_high & 0xfff); |
| 1769 const int32_t patched_movw = |
| 1770 0xe300c000 | ((dest_low >> 12) << 16) | (dest_low & 0xfff); |
| 1771 |
| 1772 region.Store<int32_t>(position, patched_movw); |
| 1773 region.Store<int32_t>(position + Instr::kInstrSize, patched_movt); |
| 1774 return; |
| 1775 } |
| 1776 |
| 1777 // If the offset loading instructions aren't there, we must have replaced |
| 1778 // the far branch with a near one, and so these instructions should be NOPs. |
| 1779 ASSERT((movt == Instr::kNopInstruction) && |
| 1780 (bx == Instr::kNopInstruction)); |
| 1781 } |
| 1782 }; |
| 1783 |
| 1784 |
| 1785 void Assembler::EmitFarBranch(Condition cond, int32_t offset, bool link) { |
| 1786 const uint16_t low = Utils::Low16Bits(offset); |
| 1787 const uint16_t high = Utils::High16Bits(offset); |
| 1788 buffer_.EmitFixup(new PatchFarBranch()); |
| 1789 movw(IP, low); |
| 1790 movt(IP, high); |
| 1791 if (link) { |
| 1792 blx(IP, cond); |
| 1793 } else { |
| 1794 bx(IP, cond); |
| 1795 } |
| 1796 } |
| 1797 |
| 1798 |
| 1799 void Assembler::EmitBranch(Condition cond, Label* label, bool link) { |
| 1800 if (label->IsBound()) { |
| 1801 const int32_t dest = label->Position() - buffer_.Size(); |
| 1802 if (FLAG_use_far_branches && !CanEncodeBranchOffset(dest)) { |
| 1803 EmitFarBranch(cond, label->Position(), link); |
| 1804 } else { |
| 1805 EmitType5(cond, dest, link); |
| 1806 } |
| 1807 } else { |
| 1808 const int position = buffer_.Size(); |
| 1809 if (FLAG_use_far_branches) { |
| 1810 const int32_t dest = label->position_; |
| 1811 EmitFarBranch(cond, dest, link); |
| 1812 } else { |
| 1813 // Use the offset field of the branch instruction for linking the sites. |
| 1814 EmitType5(cond, label->position_, link); |
| 1815 } |
| 1816 label->LinkTo(position); |
| 1817 } |
| 1818 } |
| 1819 |
| 1820 |
| 1728 void Assembler::Bind(Label* label) { | 1821 void Assembler::Bind(Label* label) { |
| 1729 ASSERT(!label->IsBound()); | 1822 ASSERT(!label->IsBound()); |
| 1730 int bound_pc = buffer_.Size(); | 1823 int bound_pc = buffer_.Size(); |
| 1731 while (label->IsLinked()) { | 1824 while (label->IsLinked()) { |
| 1732 int32_t position = label->Position(); | 1825 const int32_t position = label->Position(); |
| 1733 int32_t next = buffer_.Load<int32_t>(position); | 1826 int32_t dest = bound_pc - position; |
| 1734 int32_t encoded = Assembler::EncodeBranchOffset(bound_pc - position, next); | 1827 if (FLAG_use_far_branches && !CanEncodeBranchOffset(dest)) { |
| 1735 buffer_.Store<int32_t>(position, encoded); | 1828 // Far branches are enabled and we can't encode the branch offset. |
| 1736 label->position_ = Assembler::DecodeBranchOffset(next); | 1829 |
| 1830 // Grab instructions that load the offset. |
| 1831 const int32_t movw = |
| 1832 buffer_.Load<int32_t>(position); |
| 1833 const int32_t movt = |
| 1834 buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| 1835 |
| 1836 // Change from relative to the branch to relative to the assembler buffer. |
| 1837 dest = buffer_.Size(); |
| 1838 const uint16_t dest_high = Utils::High16Bits(dest); |
| 1839 const uint16_t dest_low = Utils::Low16Bits(dest); |
| 1840 const int32_t patched_movt = |
| 1841 0xe340c000 | ((dest_high >> 12) << 16) | (dest_high & 0xfff); |
| 1842 const int32_t patched_movw = |
| 1843 0xe300c000 | ((dest_low >> 12) << 16) | (dest_low & 0xfff); |
| 1844 |
| 1845 // Rewrite the instructions. |
| 1846 buffer_.Store<int32_t>(position, patched_movw); |
| 1847 buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, patched_movt); |
| 1848 label->position_ = DecodeLoadImmediate(movt, movw); |
| 1849 } else if (FLAG_use_far_branches && CanEncodeBranchOffset(dest)) { |
| 1850 // Far branches are enabled, but we can encode the branch offset. |
| 1851 |
| 1852 // Grab instructions that load the offset, and the branch. |
| 1853 const int32_t movw = |
| 1854 buffer_.Load<int32_t>(position); |
| 1855 const int32_t movt = |
| 1856 buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| 1857 const int32_t branch = |
| 1858 buffer_.Load<int32_t>(position + 2 * Instr::kInstrSize); |
| 1859 |
| 1860 // Grab the branch condition, and encode the link bit. |
| 1861 const int32_t cond = branch & 0xf0000000; |
| 1862 const int32_t link = (branch & 0x20) << 19; |
| 1863 |
| 1864 // Encode the branch and the offset. |
| 1865 const int32_t new_branch = cond | link | 0x0a000000; |
| 1866 const int32_t encoded = EncodeBranchOffset(dest, new_branch); |
| 1867 |
| 1868 // Write the encoded branch instruction followed by two nops. |
| 1869 buffer_.Store<int32_t>(position, encoded); |
| 1870 buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, |
| 1871 Instr::kNopInstruction); |
| 1872 buffer_.Store<int32_t>(position + 2 * Instr::kInstrSize, |
| 1873 Instr::kNopInstruction); |
| 1874 |
| 1875 label->position_ = DecodeLoadImmediate(movt, movw); |
| 1876 } else { |
| 1877 int32_t next = buffer_.Load<int32_t>(position); |
| 1878 int32_t encoded = Assembler::EncodeBranchOffset(dest, next); |
| 1879 buffer_.Store<int32_t>(position, encoded); |
| 1880 label->position_ = Assembler::DecodeBranchOffset(next); |
| 1881 } |
| 1737 } | 1882 } |
| 1738 label->BindTo(bound_pc); | 1883 label->BindTo(bound_pc); |
| 1739 } | 1884 } |
| 1740 | 1885 |
| 1741 | 1886 |
| 1742 bool Address::CanHoldLoadOffset(OperandSize type, | 1887 bool Address::CanHoldLoadOffset(OperandSize type, |
| 1743 int32_t offset, | 1888 int32_t offset, |
| 1744 int32_t* offset_mask) { | 1889 int32_t* offset_mask) { |
| 1745 switch (type) { | 1890 switch (type) { |
| 1746 case kByte: | 1891 case kByte: |
| (...skipping 661 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2408 | 2553 |
| 2409 // On entry to a function compiled for OSR, the caller's frame pointer, the | 2554 // On entry to a function compiled for OSR, the caller's frame pointer, the |
| 2410 // stack locals, and any copied parameters are already in place. The frame | 2555 // stack locals, and any copied parameters are already in place. The frame |
| 2411 // pointer is already set up. The PC marker is not correct for the | 2556 // pointer is already set up. The PC marker is not correct for the |
| 2412 // optimized function and there may be extra space for spill slots to | 2557 // optimized function and there may be extra space for spill slots to |
| 2413 // allocate. We must also set up the pool pointer for the function. | 2558 // allocate. We must also set up the pool pointer for the function. |
| 2414 void Assembler::EnterOsrFrame(intptr_t extra_size) { | 2559 void Assembler::EnterOsrFrame(intptr_t extra_size) { |
| 2415 const intptr_t offset = CodeSize(); | 2560 const intptr_t offset = CodeSize(); |
| 2416 | 2561 |
| 2417 Comment("EnterOsrFrame"); | 2562 Comment("EnterOsrFrame"); |
| 2418 mov(TMP, ShifterOperand(PC)); | 2563 mov(IP, ShifterOperand(PC)); |
| 2419 | 2564 |
| 2420 AddImmediate(TMP, -offset); | 2565 AddImmediate(IP, -offset); |
| 2421 str(TMP, Address(FP, kPcMarkerSlotFromFp * kWordSize)); | 2566 str(IP, Address(FP, kPcMarkerSlotFromFp * kWordSize)); |
| 2422 | 2567 |
| 2423 // Setup pool pointer for this dart function. | 2568 // Setup pool pointer for this dart function. |
| 2424 LoadPoolPointer(); | 2569 LoadPoolPointer(); |
| 2425 | 2570 |
| 2426 AddImmediate(SP, -extra_size); | 2571 AddImmediate(SP, -extra_size); |
| 2427 } | 2572 } |
| 2428 | 2573 |
| 2429 | 2574 |
| 2430 void Assembler::LeaveDartFrame() { | 2575 void Assembler::LeaveDartFrame() { |
| 2431 LeaveFrame((1 << PP) | (1 << FP) | (1 << LR)); | 2576 LeaveFrame((1 << PP) | (1 << FP) | (1 << LR)); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2466 Register instance_reg) { | 2611 Register instance_reg) { |
| 2467 ASSERT(failure != NULL); | 2612 ASSERT(failure != NULL); |
| 2468 if (FLAG_inline_alloc) { | 2613 if (FLAG_inline_alloc) { |
| 2469 Heap* heap = Isolate::Current()->heap(); | 2614 Heap* heap = Isolate::Current()->heap(); |
| 2470 const intptr_t instance_size = cls.instance_size(); | 2615 const intptr_t instance_size = cls.instance_size(); |
| 2471 LoadImmediate(instance_reg, heap->TopAddress()); | 2616 LoadImmediate(instance_reg, heap->TopAddress()); |
| 2472 ldr(instance_reg, Address(instance_reg, 0)); | 2617 ldr(instance_reg, Address(instance_reg, 0)); |
| 2473 AddImmediate(instance_reg, instance_size); | 2618 AddImmediate(instance_reg, instance_size); |
| 2474 | 2619 |
| 2475 // instance_reg: potential next object start. | 2620 // instance_reg: potential next object start. |
| 2476 LoadImmediate(TMP, heap->EndAddress()); | 2621 LoadImmediate(IP, heap->EndAddress()); |
| 2477 ldr(TMP, Address(TMP, 0)); | 2622 ldr(IP, Address(IP, 0)); |
| 2478 cmp(TMP, ShifterOperand(instance_reg)); | 2623 cmp(IP, ShifterOperand(instance_reg)); |
| 2479 // fail if heap end unsigned less than or equal to instance_reg. | 2624 // fail if heap end unsigned less than or equal to instance_reg. |
| 2480 b(failure, LS); | 2625 b(failure, LS); |
| 2481 | 2626 |
| 2482 // Successfully allocated the object, now update top to point to | 2627 // Successfully allocated the object, now update top to point to |
| 2483 // next object start and store the class in the class field of object. | 2628 // next object start and store the class in the class field of object. |
| 2484 LoadImmediate(TMP, heap->TopAddress()); | 2629 LoadImmediate(IP, heap->TopAddress()); |
| 2485 str(instance_reg, Address(TMP, 0)); | 2630 str(instance_reg, Address(IP, 0)); |
| 2486 | 2631 |
| 2487 ASSERT(instance_size >= kHeapObjectTag); | 2632 ASSERT(instance_size >= kHeapObjectTag); |
| 2488 AddImmediate(instance_reg, -instance_size + kHeapObjectTag); | 2633 AddImmediate(instance_reg, -instance_size + kHeapObjectTag); |
| 2489 | 2634 |
| 2490 uword tags = 0; | 2635 uword tags = 0; |
| 2491 tags = RawObject::SizeTag::update(instance_size, tags); | 2636 tags = RawObject::SizeTag::update(instance_size, tags); |
| 2492 ASSERT(cls.id() != kIllegalCid); | 2637 ASSERT(cls.id() != kIllegalCid); |
| 2493 tags = RawObject::ClassIdTag::update(cls.id(), tags); | 2638 tags = RawObject::ClassIdTag::update(cls.id(), tags); |
| 2494 LoadImmediate(TMP, tags); | 2639 LoadImmediate(IP, tags); |
| 2495 str(TMP, FieldAddress(instance_reg, Object::tags_offset())); | 2640 str(IP, FieldAddress(instance_reg, Object::tags_offset())); |
| 2496 } else { | 2641 } else { |
| 2497 b(failure); | 2642 b(failure); |
| 2498 } | 2643 } |
| 2499 } | 2644 } |
| 2500 | 2645 |
| 2501 | 2646 |
| 2502 void Assembler::Stop(const char* message) { | 2647 void Assembler::Stop(const char* message) { |
| 2503 if (FLAG_print_stop_message) { | 2648 if (FLAG_print_stop_message) { |
| 2504 PushList((1 << R0) | (1 << IP) | (1 << LR)); // Preserve R0, IP, LR. | 2649 PushList((1 << R0) | (1 << IP) | (1 << LR)); // Preserve R0, IP, LR. |
| 2505 LoadImmediate(R0, reinterpret_cast<int32_t>(message)); | 2650 LoadImmediate(R0, reinterpret_cast<int32_t>(message)); |
| 2506 // PrintStopMessage() preserves all registers. | 2651 // PrintStopMessage() preserves all registers. |
| 2507 BranchLink(&StubCode::PrintStopMessageLabel()); // Passing message in R0. | 2652 BranchLink(&StubCode::PrintStopMessageLabel()); // Passing message in R0. |
| 2508 PopList((1 << R0) | (1 << IP) | (1 << LR)); // Restore R0, IP, LR. | 2653 PopList((1 << R0) | (1 << IP) | (1 << LR)); // Restore R0, IP, LR. |
| 2509 } | 2654 } |
| 2510 // Emit the message address before the svc instruction, so that we can | 2655 // Emit the message address before the svc instruction, so that we can |
| 2511 // 'unstop' and continue execution in the simulator or jump to the next | 2656 // 'unstop' and continue execution in the simulator or jump to the next |
| 2512 // instruction in gdb. | 2657 // instruction in gdb. |
| 2513 Label stop; | 2658 Label stop; |
| 2514 b(&stop); | 2659 b(&stop); |
| 2515 Emit(reinterpret_cast<int32_t>(message)); | 2660 Emit(reinterpret_cast<int32_t>(message)); |
| 2516 Bind(&stop); | 2661 Bind(&stop); |
| 2517 svc(kStopMessageSvcCode); | 2662 svc(kStopMessageSvcCode); |
| 2518 } | 2663 } |
| 2519 | 2664 |
| 2520 | 2665 |
| 2521 int32_t Assembler::EncodeBranchOffset(int32_t offset, int32_t inst) { | |
| 2522 // The offset is off by 8 due to the way the ARM CPUs read PC. | |
| 2523 offset -= 8; | |
| 2524 ASSERT(Utils::IsAligned(offset, 4)); | |
| 2525 ASSERT(Utils::IsInt(Utils::CountOneBits(kBranchOffsetMask), offset)); | |
| 2526 | |
| 2527 // Properly preserve only the bits supported in the instruction. | |
| 2528 offset >>= 2; | |
| 2529 offset &= kBranchOffsetMask; | |
| 2530 return (inst & ~kBranchOffsetMask) | offset; | |
| 2531 } | |
| 2532 | |
| 2533 | |
| 2534 int Assembler::DecodeBranchOffset(int32_t inst) { | |
| 2535 // Sign-extend, left-shift by 2, then add 8. | |
| 2536 return ((((inst & kBranchOffsetMask) << 8) >> 6) + 8); | |
| 2537 } | |
| 2538 | |
| 2539 | |
| 2540 int32_t Assembler::AddObject(const Object& obj) { | 2666 int32_t Assembler::AddObject(const Object& obj) { |
| 2541 ASSERT(obj.IsNotTemporaryScopedHandle()); | 2667 ASSERT(obj.IsNotTemporaryScopedHandle()); |
| 2542 ASSERT(obj.IsOld()); | 2668 ASSERT(obj.IsOld()); |
| 2543 if (object_pool_.IsNull()) { | 2669 if (object_pool_.IsNull()) { |
| 2544 // The object pool cannot be used in the vm isolate. | 2670 // The object pool cannot be used in the vm isolate. |
| 2545 ASSERT(Isolate::Current() != Dart::vm_isolate()); | 2671 ASSERT(Isolate::Current() != Dart::vm_isolate()); |
| 2546 object_pool_ = GrowableObjectArray::New(Heap::kOld); | 2672 object_pool_ = GrowableObjectArray::New(Heap::kOld); |
| 2547 } | 2673 } |
| 2548 for (int i = 0; i < object_pool_.Length(); i++) { | 2674 for (int i = 0; i < object_pool_.Length(); i++) { |
| 2549 if (object_pool_.At(i) == obj.raw()) { | 2675 if (object_pool_.At(i) == obj.raw()) { |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2594 | 2720 |
| 2595 const char* Assembler::FpuRegisterName(FpuRegister reg) { | 2721 const char* Assembler::FpuRegisterName(FpuRegister reg) { |
| 2596 ASSERT((0 <= reg) && (reg < kNumberOfFpuRegisters)); | 2722 ASSERT((0 <= reg) && (reg < kNumberOfFpuRegisters)); |
| 2597 return fpu_reg_names[reg]; | 2723 return fpu_reg_names[reg]; |
| 2598 } | 2724 } |
| 2599 | 2725 |
| 2600 } // namespace dart | 2726 } // namespace dart |
| 2601 | 2727 |
| 2602 #endif // defined TARGET_ARCH_ARM | 2728 #endif // defined TARGET_ARCH_ARM |
| 2603 | 2729 |
| OLD | NEW |