24 */
25
26 #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
27 #include "gc/shenandoah/mode/shenandoahMode.hpp"
28 #include "gc/shenandoah/shenandoahBarrierSet.hpp"
29 #include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
30 #include "gc/shenandoah/shenandoahForwarding.hpp"
31 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
32 #include "gc/shenandoah/shenandoahHeapRegion.hpp"
33 #include "gc/shenandoah/shenandoahRuntime.hpp"
34 #include "gc/shenandoah/shenandoahThreadLocalData.hpp"
35 #include "interpreter/interpreter.hpp"
36 #include "runtime/javaThread.hpp"
37 #include "runtime/sharedRuntime.hpp"
38 #include "utilities/macros.hpp"
39 #ifdef COMPILER1
40 #include "c1/c1_LIRAssembler.hpp"
41 #include "c1/c1_MacroAssembler.hpp"
42 #include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp"
43 #endif
44
45 #define __ masm->
46
47 static void save_machine_state(MacroAssembler* masm, bool handle_gpr, bool handle_fp) {
48 if (handle_gpr) {
49 __ push_IU_state();
50 }
51
52 if (handle_fp) {
53 // Some paths can be reached from the c2i adapter with live fp arguments in registers.
54 assert(Argument::n_float_register_parameters_j == 8, "8 fp registers to save at java call");
55
56 const int xmm_size = wordSize * 2;
57 __ subptr(rsp, xmm_size * 8);
58 __ movdbl(Address(rsp, xmm_size * 0), xmm0);
59 __ movdbl(Address(rsp, xmm_size * 1), xmm1);
60 __ movdbl(Address(rsp, xmm_size * 2), xmm2);
61 __ movdbl(Address(rsp, xmm_size * 3), xmm3);
62 __ movdbl(Address(rsp, xmm_size * 4), xmm4);
63 __ movdbl(Address(rsp, xmm_size * 5), xmm5);
655 if (UseCompressedOops) {
656 __ lock();
657 __ cmpxchgl(newval, addr);
658 } else {
659 __ lock();
660 __ cmpxchgptr(newval, addr);
661 }
662 __ jcc(Assembler::equal, L_success);
663
664 // Step 2. CAS had failed. This may be a false negative.
665 //
666 // The trouble comes when we compare the to-space pointer with the from-space
667 // pointer to the same object. To resolve this, it will suffice to resolve
668 // the value from memory -- this will give both to-space pointers.
669 // If they mismatch, then it was a legitimate failure.
670 //
671 // Before reaching to resolve sequence, see if we can avoid the whole shebang
672 // with filters.
673
674 // Filter: when offending in-memory value is null, the failure is definitely legitimate
675 __ testptr(oldval, oldval);
676 __ jcc(Assembler::zero, L_failure);
677
678 // Filter: when heap is stable, the failure is definitely legitimate
679 const Register thread = r15_thread;
680 Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
681 __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED);
682 __ jcc(Assembler::zero, L_failure);
683
684 if (UseCompressedOops) {
685 __ movl(tmp2, oldval);
686 __ decode_heap_oop(tmp2);
687 } else {
688 __ movptr(tmp2, oldval);
689 }
690
691 // Decode offending in-memory value.
692 // Test if-forwarded
693 __ testb(Address(tmp2, oopDesc::mark_offset_in_bytes()), markWord::marked_value);
694 __ jcc(Assembler::noParity, L_failure); // When odd number of bits, then not forwarded
695 __ jcc(Assembler::zero, L_failure); // When it is 00, then also not forwarded
757 // and promote the result. Note that we handle the flag from both the 1st and 2nd CAS.
758 // Otherwise, failure witness for CAE is in oldval on all paths, and we can return.
759
760 if (exchange) {
761 __ bind(L_failure);
762 __ bind(L_success);
763 } else {
764 assert(res != noreg, "need result register");
765
766 Label exit;
767 __ bind(L_failure);
768 __ xorptr(res, res);
769 __ jmpb(exit);
770
771 __ bind(L_success);
772 __ movptr(res, 1);
773 __ bind(exit);
774 }
775 }
776
777 #ifdef PRODUCT
778 #define BLOCK_COMMENT(str) /* nothing */
779 #else
780 #define BLOCK_COMMENT(str) __ block_comment(str)
781 #endif
782
783 #define BIND(label) bind(label); BLOCK_COMMENT(#label ":")
784
785 #define TIMES_OOP (UseCompressedOops ? Address::times_4 : Address::times_8)
786
787 void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
788 Register addr, Register count,
789 Register tmp) {
790 assert(ShenandoahCardBarrier, "Should have been checked by caller");
791
792 Label L_loop, L_done;
793 const Register end = count;
794 assert_different_registers(addr, end);
795
796 // Zero count? Nothing to do.
|
24 */
25
26 #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
27 #include "gc/shenandoah/mode/shenandoahMode.hpp"
28 #include "gc/shenandoah/shenandoahBarrierSet.hpp"
29 #include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp"
30 #include "gc/shenandoah/shenandoahForwarding.hpp"
31 #include "gc/shenandoah/shenandoahHeap.inline.hpp"
32 #include "gc/shenandoah/shenandoahHeapRegion.hpp"
33 #include "gc/shenandoah/shenandoahRuntime.hpp"
34 #include "gc/shenandoah/shenandoahThreadLocalData.hpp"
35 #include "interpreter/interpreter.hpp"
36 #include "runtime/javaThread.hpp"
37 #include "runtime/sharedRuntime.hpp"
38 #include "utilities/macros.hpp"
39 #ifdef COMPILER1
40 #include "c1/c1_LIRAssembler.hpp"
41 #include "c1/c1_MacroAssembler.hpp"
42 #include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp"
43 #endif
44 #ifdef COMPILER2
45 #include "gc/shenandoah/c2/shenandoahBarrierSetC2.hpp"
46 #endif
47
48 #define __ masm->
49
50 static void save_machine_state(MacroAssembler* masm, bool handle_gpr, bool handle_fp) {
51 if (handle_gpr) {
52 __ push_IU_state();
53 }
54
55 if (handle_fp) {
56 // Some paths can be reached from the c2i adapter with live fp arguments in registers.
57 assert(Argument::n_float_register_parameters_j == 8, "8 fp registers to save at java call");
58
59 const int xmm_size = wordSize * 2;
60 __ subptr(rsp, xmm_size * 8);
61 __ movdbl(Address(rsp, xmm_size * 0), xmm0);
62 __ movdbl(Address(rsp, xmm_size * 1), xmm1);
63 __ movdbl(Address(rsp, xmm_size * 2), xmm2);
64 __ movdbl(Address(rsp, xmm_size * 3), xmm3);
65 __ movdbl(Address(rsp, xmm_size * 4), xmm4);
66 __ movdbl(Address(rsp, xmm_size * 5), xmm5);
658 if (UseCompressedOops) {
659 __ lock();
660 __ cmpxchgl(newval, addr);
661 } else {
662 __ lock();
663 __ cmpxchgptr(newval, addr);
664 }
665 __ jcc(Assembler::equal, L_success);
666
667 // Step 2. CAS had failed. This may be a false negative.
668 //
669 // The trouble comes when we compare the to-space pointer with the from-space
670 // pointer to the same object. To resolve this, it will suffice to resolve
671 // the value from memory -- this will give both to-space pointers.
672 // If they mismatch, then it was a legitimate failure.
673 //
674 // Before reaching to resolve sequence, see if we can avoid the whole shebang
675 // with filters.
676
677 // Filter: when offending in-memory value is null, the failure is definitely legitimate
678 if (UseCompressedOops) {
679 __ testl(oldval, oldval);
680 } else {
681 __ testptr(oldval, oldval);
682 }
683 __ jcc(Assembler::zero, L_failure);
684
685 // Filter: when heap is stable, the failure is definitely legitimate
686 const Register thread = r15_thread;
687 Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
688 __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED);
689 __ jcc(Assembler::zero, L_failure);
690
691 if (UseCompressedOops) {
692 __ movl(tmp2, oldval);
693 __ decode_heap_oop(tmp2);
694 } else {
695 __ movptr(tmp2, oldval);
696 }
697
698 // Decode offending in-memory value.
699 // Test if-forwarded
700 __ testb(Address(tmp2, oopDesc::mark_offset_in_bytes()), markWord::marked_value);
701 __ jcc(Assembler::noParity, L_failure); // When odd number of bits, then not forwarded
702 __ jcc(Assembler::zero, L_failure); // When it is 00, then also not forwarded
764 // and promote the result. Note that we handle the flag from both the 1st and 2nd CAS.
765 // Otherwise, failure witness for CAE is in oldval on all paths, and we can return.
766
767 if (exchange) {
768 __ bind(L_failure);
769 __ bind(L_success);
770 } else {
771 assert(res != noreg, "need result register");
772
773 Label exit;
774 __ bind(L_failure);
775 __ xorptr(res, res);
776 __ jmpb(exit);
777
778 __ bind(L_success);
779 __ movptr(res, 1);
780 __ bind(exit);
781 }
782 }
783
784 #ifdef COMPILER2
785 void ShenandoahBarrierSetAssembler::load_ref_barrier_c2(const MachNode* node, MacroAssembler* masm, Register obj, Register addr, Register tmp1, Register tmp2, Register tmp3, bool narrow) {
786 if (!ShenandoahLoadRefBarrierStubC2::needs_barrier(node)) {
787 return;
788 }
789 Assembler::InlineSkippedInstructionsCounter skip_counter(masm);
790
791 ShenandoahLoadRefBarrierStubC2* const stub = ShenandoahLoadRefBarrierStubC2::create(node, obj, addr, tmp1, tmp2, tmp3, narrow);
792 stub->dont_preserve(obj); // set at the end, no need to save
793 if (tmp1 != noreg) {
794 stub->dont_preserve(tmp1); // temp, no need to save
795 }
796 if (tmp2 != noreg) {
797 stub->dont_preserve(tmp2); // temp, no need to save
798 }
799 if (tmp3 != noreg) {
800 stub->dont_preserve(tmp3); // temp, no need to save
801 }
802
803 Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
804 int flags = ShenandoahHeap::HAS_FORWARDED;
805 bool is_strong = (node->barrier_data() & ShenandoahBarrierStrong) != 0;
806 if (!is_strong) {
807 flags |= ShenandoahHeap::WEAK_ROOTS;
808 }
809 __ testb(gc_state, flags);
810 __ jcc(Assembler::notZero, *stub->entry());
811 __ bind(*stub->continuation());
812 }
813
814 void ShenandoahBarrierSetAssembler::satb_barrier_c2(const MachNode* node, MacroAssembler* masm,
815 Register addr, Register preval, Register tmp) {
816 if (!ShenandoahSATBBarrierStubC2::needs_barrier(node)) {
817 return;
818 }
819 ShenandoahSATBBarrierStubC2* const stub = ShenandoahSATBBarrierStubC2::create(node, addr, preval, tmp);
820 Assembler::InlineSkippedInstructionsCounter skip_counter(masm);
821 Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
822 __ testb(gc_state, ShenandoahHeap::MARKING);
823 __ jcc(Assembler::notZero, *stub->entry());
824 __ bind(*stub->continuation());
825 }
826
827 void ShenandoahBarrierSetAssembler::card_barrier_c2(const MachNode* node, MacroAssembler* masm,
828 Register addr, Register addr_tmp, Register tmp) {
829 if (!ShenandoahCardBarrier ||
830 (node->barrier_data() & (ShenandoahBarrierCardMark | ShenandoahBarrierCardMarkNotNull)) == 0) {
831 return;
832 }
833 Assembler::InlineSkippedInstructionsCounter skip_counter(masm);
834 if (addr != noreg) {
835 __ mov(addr_tmp, addr);
836 }
837 __ shrptr(addr_tmp, CardTable::card_shift());
838
839 Address curr_ct_holder_addr(r15_thread, in_bytes(ShenandoahThreadLocalData::card_table_offset()));
840 __ movptr(tmp, curr_ct_holder_addr);
841 Address card_addr(tmp, addr_tmp, Address::times_1);
842
843 int dirty = CardTable::dirty_card_val();
844 if (UseCondCardMark) {
845 Label L_already_dirty;
846 __ cmpb(card_addr, dirty);
847 __ jccb(Assembler::equal, L_already_dirty);
848 __ movb(card_addr, dirty);
849 __ bind(L_already_dirty);
850 } else {
851 __ movb(card_addr, dirty);
852 }
853 }
854
855 void ShenandoahBarrierSetAssembler::cmpxchg_oop_c2(const MachNode* node, MacroAssembler* masm,
856 Register res, Address addr, Register oldval, Register newval, Register tmp1, Register tmp2,
857 bool exchange) {
858 assert(oldval == rax, "must be in rax for implicit use in cmpxchg");
859 assert_different_registers(oldval, tmp1, tmp2);
860 assert_different_registers(newval, tmp1, tmp2);
861
862 // Remember oldval for retry logic in slow path. We need to do it here,
863 // because it will be overwritten by the fast-path CAS.
864 if (ShenandoahCASBarrier) {
865 __ movptr(tmp2, oldval);
866 }
867
868 // Fast-path: Try to CAS optimistically. If successful, then we are done.
869 __ lock();
870 if (UseCompressedOops) {
871 __ cmpxchgl(newval, addr);
872 } else {
873 __ cmpxchgptr(newval, addr);
874 }
875
876 // If we need a boolean result out of CAS, set the flag appropriately and promote the result.
877 // This would be the final result if we do not go slow.
878 if (!exchange) {
879 assert(res != noreg, "need result register");
880 __ setcc(Assembler::equal, res);
881 } else {
882 assert(res == noreg, "no result expected");
883 }
884
885 if (ShenandoahCASBarrier) {
886 ShenandoahCASBarrierSlowStubC2* const slow_stub =
887 ShenandoahCASBarrierSlowStubC2::create(node, addr, oldval, newval, res, tmp1, tmp2, exchange);
888 if (res != noreg) {
889 slow_stub->dont_preserve(res); // set at the end, no need to save
890 }
891 slow_stub->dont_preserve(oldval); // saved explicitly
892 slow_stub->dont_preserve(tmp1); // temp, no need to save
893 slow_stub->dont_preserve(tmp2); // temp, no need to save
894
895 // On success, we do not need any additional handling.
896 __ jccb(Assembler::equal, *slow_stub->continuation());
897
898 // If GC is in progress, it is likely we need additional handling for false negatives.
899 Address gc_state(r15_thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset()));
900 __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED);
901 __ jcc(Assembler::notZero, *slow_stub->entry());
902
903 // Slow stub re-enters with result set correctly.
904 __ bind(*slow_stub->continuation());
905 }
906 }
907
908 #undef __
909 #define __ masm.
910
911 void ShenandoahLoadRefBarrierStubC2::emit_code(MacroAssembler& masm) {
912 Assembler::InlineSkippedInstructionsCounter skip_counter(&masm);
913 __ bind(*entry());
914
915 Register obj = _obj;
916 if (_narrow) {
917 __ movl(_tmp1, _obj);
918 __ decode_heap_oop(_tmp1);
919 obj = _tmp1;
920 }
921
922 // Weak/phantom loads always need to go to runtime.
923 if ((_node->barrier_data() & ShenandoahBarrierStrong) != 0) {
924 __ movptr(_tmp2, obj);
925 __ shrptr(_tmp2, ShenandoahHeapRegion::region_size_bytes_shift_jint());
926 __ movptr(_tmp3, (intptr_t) ShenandoahHeap::in_cset_fast_test_addr());
927 __ movbool(_tmp2, Address(_tmp2, _tmp3, Address::times_1));
928 __ testbool(_tmp2);
929 __ jcc(Assembler::zero, *continuation());
930 }
931
932 {
933 SaveLiveRegisters save_registers(&masm, this);
934 if (c_rarg0 != obj) {
935 if (c_rarg0 == _addr) {
936 __ movptr(_tmp2, _addr);
937 _addr = _tmp2;
938 }
939 __ movptr(c_rarg0, obj);
940 }
941 if (c_rarg1 != _addr) {
942 __ movptr(c_rarg1, _addr);
943 }
944
945 address entry;
946 if (_narrow) {
947 if ((_node->barrier_data() & ShenandoahBarrierStrong) != 0) {
948 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow);
949 } else if ((_node->barrier_data() & ShenandoahBarrierWeak) != 0) {
950 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow);
951 } else if ((_node->barrier_data() & ShenandoahBarrierPhantom) != 0) {
952 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom_narrow);
953 }
954 } else {
955 if ((_node->barrier_data() & ShenandoahBarrierStrong) != 0) {
956 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong);
957 } else if ((_node->barrier_data() & ShenandoahBarrierWeak) != 0) {
958 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak);
959 } else if ((_node->barrier_data() & ShenandoahBarrierPhantom) != 0) {
960 entry = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom);
961 }
962 }
963 __ call(RuntimeAddress(entry), rax);
964 assert(!save_registers.contains(_obj), "must not save result register");
965 __ movptr(_obj, rax);
966 }
967 if (_narrow) {
968 __ encode_heap_oop(_obj);
969 }
970
971 __ jmp(*continuation());
972 }
973
974 void ShenandoahSATBBarrierStubC2::emit_code(MacroAssembler& masm) {
975 __ bind(*entry());
976 Address index(r15_thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset()));
977 Address buffer(r15_thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset()));
978
979 Label runtime;
980
981 // Do we need to load the previous value?
982 if (_addr != noreg) {
983 __ load_heap_oop(_preval, Address(_addr, 0), noreg, AS_RAW);
984 }
985 // Is the previous value null?
986 __ cmpptr(_preval, NULL_WORD);
987 __ jcc(Assembler::equal, *continuation());
988
989 // Can we store a value in the given thread's buffer?
990 // (The index field is typed as size_t.)
991 __ movptr(_tmp, index);
992 __ testptr(_tmp, _tmp);
993 __ jccb(Assembler::zero, runtime);
994 // The buffer is not full, store value into it.
995 __ subptr(_tmp, wordSize);
996 __ movptr(index, _tmp);
997 __ addptr(_tmp, buffer);
998 __ movptr(Address(_tmp, 0), _preval);
999
1000 __ jmp(*continuation());
1001
1002 __ bind(runtime);
1003 {
1004 SaveLiveRegisters save_registers(&masm, this);
1005 if (c_rarg0 != _preval) {
1006 __ mov(c_rarg0, _preval);
1007 }
1008 // rax is a caller-saved, non-argument-passing register, so it does not
1009 // interfere with c_rarg0 or c_rarg1. If it contained any live value before
1010 // entering this stub, it is saved at this point, and restored after the
1011 // call. If it did not contain any live value, it is free to be used. In
1012 // either case, it is safe to use it here as a call scratch register.
1013 __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre_c2)), rax);
1014 }
1015 __ jmp(*continuation());
1016 }
1017
1018 void ShenandoahCASBarrierMidStubC2::emit_code(MacroAssembler& masm) {
1019 // x86_64 does not implement this.
1020 ShouldNotReachHere();
1021 }
1022
1023 void ShenandoahCASBarrierSlowStubC2::emit_code(MacroAssembler& masm) {
1024 __ bind(*entry());
1025
1026 // CAS has failed because the value held at addr does not match expected.
1027 // This may be a false negative because the version in memory might be
1028 // the from-space version of the same object we currently hold to-space
1029 // reference for.
1030 //
1031 // To resolve this, we need to pass the location through the LRB fixup,
1032 // this will make sure that the location has only to-space pointers.
1033 // To avoid calling into runtime often, we cset-check the object first.
1034 // We can inline most of the work here, but there is little point,
1035 // as CAS failures over cset locations must be rare. This fast-slow split
1036 // matches what we do for normal LRB.
1037
1038 assert(_expected == rax, "expected must be rax");
1039
1040 // Non-strong references should always go to runtime. We do not expect
1041 // CASes over non-strong locations.
1042 assert((_node->barrier_data() & ShenandoahBarrierStrong) != 0, "Only strong references for CASes");
1043
1044 Label L_final;
1045
1046 // Fast-path stashed original oldval to tmp2 for us. We need to save it
1047 // for the final retry. This frees up tmp2 for cset check below.
1048 __ push(_tmp2);
1049
1050 // (Compressed) failure witness is in _expected.
1051 // Unpack it and check if it is in collection set.
1052 __ movptr(_tmp1, _expected);
1053 if (UseCompressedOops) {
1054 __ decode_heap_oop(_tmp1);
1055 }
1056 __ shrptr(_tmp1, ShenandoahHeapRegion::region_size_bytes_shift_jint());
1057 __ movptr(_tmp2, (intptr_t) ShenandoahHeap::in_cset_fast_test_addr());
1058 __ movbool(_tmp1, Address(_tmp1, _tmp2, Address::times_1));
1059 __ testbool(_tmp1);
1060 __ jcc(Assembler::zero, L_final);
1061
1062 {
1063 SaveLiveRegisters save_registers(&masm, this);
1064 // Load up failure witness again.
1065 if (c_rarg0 != _expected) {
1066 __ movptr(c_rarg0, _expected);
1067 }
1068 if (UseCompressedOops) {
1069 __ decode_heap_oop(c_rarg0);
1070 }
1071 __ lea(c_rarg1, _addr);
1072
1073 if (UseCompressedOops) {
1074 __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow), 2);
1075 } else {
1076 __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong), 2);
1077 }
1078 // We have called LRB to fix up the heap location. We do not care about its result,
1079 // as we will just try to CAS the location again.
1080 }
1081
1082 __ bind(L_final);
1083
1084 // Try to CAS again with the original expected value.
1085 // At this point, there can no longer be false negatives.
1086 __ pop(_expected);
1087 __ lock();
1088 if (UseCompressedOops) {
1089 __ cmpxchgl(_new_val, _addr);
1090 } else {
1091 __ cmpxchgptr(_new_val, _addr);
1092 }
1093 if (!_cae) {
1094 assert(_result != noreg, "need result register");
1095 __ setcc(Assembler::equal, _result);
1096 } else {
1097 assert(_result == noreg, "no result expected");
1098 }
1099 __ jmp(*continuation());
1100 }
1101
1102 #undef __
1103 #define __ masm->
1104 #endif
1105
1106 #ifdef PRODUCT
1107 #define BLOCK_COMMENT(str) /* nothing */
1108 #else
1109 #define BLOCK_COMMENT(str) __ block_comment(str)
1110 #endif
1111
1112 #define BIND(label) bind(label); BLOCK_COMMENT(#label ":")
1113
1114 #define TIMES_OOP (UseCompressedOops ? Address::times_4 : Address::times_8)
1115
1116 void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators,
1117 Register addr, Register count,
1118 Register tmp) {
1119 assert(ShenandoahCardBarrier, "Should have been checked by caller");
1120
1121 Label L_loop, L_done;
1122 const Register end = count;
1123 assert_different_registers(addr, end);
1124
1125 // Zero count? Nothing to do.
|