提交 f299daf0 编写于 作者: K kvn

6614597: Performance variability in jvm2008 xml.validation

Summary: Fix incorrect marking of methods as not compilable.
Reviewed-by: never
上级 7ecf2ea5
...@@ -1681,11 +1681,8 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, ...@@ -1681,11 +1681,8 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver,
// If no method data exists, go to profile_continue. // If no method data exists, go to profile_continue.
test_method_data_pointer(profile_continue); test_method_data_pointer(profile_continue);
// We are making a call. Increment the count.
increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch);
// Record the receiver type. // Record the receiver type.
record_klass_in_profile(receiver, scratch); record_klass_in_profile(receiver, scratch, true);
// The method data pointer needs to be updated to reflect the new target. // The method data pointer needs to be updated to reflect the new target.
update_mdp_by_constant(in_bytes(VirtualCallData::virtual_call_data_size())); update_mdp_by_constant(in_bytes(VirtualCallData::virtual_call_data_size()));
...@@ -1695,9 +1692,13 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, ...@@ -1695,9 +1692,13 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver,
void InterpreterMacroAssembler::record_klass_in_profile_helper( void InterpreterMacroAssembler::record_klass_in_profile_helper(
Register receiver, Register scratch, Register receiver, Register scratch,
int start_row, Label& done) { int start_row, Label& done, bool is_virtual_call) {
if (TypeProfileWidth == 0) if (TypeProfileWidth == 0) {
if (is_virtual_call) {
increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch);
}
return; return;
}
int last_row = VirtualCallData::row_limit() - 1; int last_row = VirtualCallData::row_limit() - 1;
assert(start_row <= last_row, "must be work left to do"); assert(start_row <= last_row, "must be work left to do");
...@@ -1714,6 +1715,7 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1714,6 +1715,7 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
// See if the receiver is receiver[n]. // See if the receiver is receiver[n].
int recvr_offset = in_bytes(VirtualCallData::receiver_offset(row)); int recvr_offset = in_bytes(VirtualCallData::receiver_offset(row));
test_mdp_data_at(recvr_offset, receiver, next_test, scratch); test_mdp_data_at(recvr_offset, receiver, next_test, scratch);
// delayed()->tst(scratch);
// The receiver is receiver[n]. Increment count[n]. // The receiver is receiver[n]. Increment count[n].
int count_offset = in_bytes(VirtualCallData::receiver_count_offset(row)); int count_offset = in_bytes(VirtualCallData::receiver_count_offset(row));
...@@ -1723,20 +1725,31 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1723,20 +1725,31 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
bind(next_test); bind(next_test);
if (test_for_null_also) { if (test_for_null_also) {
Label found_null;
// Failed the equality check on receiver[n]... Test for null. // Failed the equality check on receiver[n]... Test for null.
if (start_row == last_row) { if (start_row == last_row) {
// The only thing left to do is handle the null case. // The only thing left to do is handle the null case.
if (is_virtual_call) {
brx(Assembler::zero, false, Assembler::pn, found_null);
delayed()->nop();
// Receiver did not match any saved receiver and there is no empty row for it.
// Increment total counter to indicate polimorphic case.
increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch);
ba(false, done);
delayed()->nop();
bind(found_null);
} else {
brx(Assembler::notZero, false, Assembler::pt, done); brx(Assembler::notZero, false, Assembler::pt, done);
delayed()->nop(); delayed()->nop();
}
break; break;
} }
// Since null is rare, make it be the branch-taken case. // Since null is rare, make it be the branch-taken case.
Label found_null;
brx(Assembler::zero, false, Assembler::pn, found_null); brx(Assembler::zero, false, Assembler::pn, found_null);
delayed()->nop(); delayed()->nop();
// Put all the "Case 3" tests here. // Put all the "Case 3" tests here.
record_klass_in_profile_helper(receiver, scratch, start_row + 1, done); record_klass_in_profile_helper(receiver, scratch, start_row + 1, done, is_virtual_call);
// Found a null. Keep searching for a matching receiver, // Found a null. Keep searching for a matching receiver,
// but remember that this is an empty (unused) slot. // but remember that this is an empty (unused) slot.
...@@ -1753,16 +1766,18 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1753,16 +1766,18 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row)); int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row));
mov(DataLayout::counter_increment, scratch); mov(DataLayout::counter_increment, scratch);
set_mdp_data_at(count_offset, scratch); set_mdp_data_at(count_offset, scratch);
if (start_row > 0) {
ba(false, done); ba(false, done);
delayed()->nop(); delayed()->nop();
}
} }
void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, void InterpreterMacroAssembler::record_klass_in_profile(Register receiver,
Register scratch) { Register scratch, bool is_virtual_call) {
assert(ProfileInterpreter, "must be profiling"); assert(ProfileInterpreter, "must be profiling");
Label done; Label done;
record_klass_in_profile_helper(receiver, scratch, 0, done); record_klass_in_profile_helper(receiver, scratch, 0, done, is_virtual_call);
bind (done); bind (done);
} }
...@@ -1840,7 +1855,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register klass, ...@@ -1840,7 +1855,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register klass,
mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size());
// Record the object type. // Record the object type.
record_klass_in_profile(klass, scratch); record_klass_in_profile(klass, scratch, false);
} }
// The method data pointer needs to be updated. // The method data pointer needs to be updated.
......
...@@ -290,9 +290,9 @@ class InterpreterMacroAssembler: public MacroAssembler { ...@@ -290,9 +290,9 @@ class InterpreterMacroAssembler: public MacroAssembler {
void test_mdp_data_at(int offset, Register value, Label& not_equal_continue, void test_mdp_data_at(int offset, Register value, Label& not_equal_continue,
Register scratch); Register scratch);
void record_klass_in_profile(Register receiver, Register scratch); void record_klass_in_profile(Register receiver, Register scratch, bool is_virtual_call);
void record_klass_in_profile_helper(Register receiver, Register scratch, void record_klass_in_profile_helper(Register receiver, Register scratch,
int start_row, Label& done); int start_row, Label& done, bool is_virtual_call);
void update_mdp_by_offset(int offset_of_disp, Register scratch); void update_mdp_by_offset(int offset_of_disp, Register scratch);
void update_mdp_by_offset(Register reg, int offset_of_disp, void update_mdp_by_offset(Register reg, int offset_of_disp,
......
...@@ -3209,7 +3209,6 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { ...@@ -3209,7 +3209,6 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) {
Register mdo = op->mdo()->as_register(); Register mdo = op->mdo()->as_register();
__ movoop(mdo, md->constant_encoding()); __ movoop(mdo, md->constant_encoding());
Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset()));
__ addl(counter_addr, DataLayout::counter_increment);
Bytecodes::Code bc = method->java_code_at_bci(bci); Bytecodes::Code bc = method->java_code_at_bci(bci);
// Perform additional virtual call profiling for invokevirtual and // Perform additional virtual call profiling for invokevirtual and
// invokeinterface bytecodes // invokeinterface bytecodes
...@@ -3276,14 +3275,18 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { ...@@ -3276,14 +3275,18 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) {
__ jcc(Assembler::notEqual, next_test); __ jcc(Assembler::notEqual, next_test);
__ movptr(recv_addr, recv); __ movptr(recv_addr, recv);
__ movl(Address(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))), DataLayout::counter_increment); __ movl(Address(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))), DataLayout::counter_increment);
if (i < (VirtualCallData::row_limit() - 1)) {
__ jmp(update_done); __ jmp(update_done);
}
__ bind(next_test); __ bind(next_test);
} }
// Receiver did not match any saved receiver and there is no empty row for it.
// Increment total counter to indicate polimorphic case.
__ addl(counter_addr, DataLayout::counter_increment);
__ bind(update_done); __ bind(update_done);
} }
} else {
// Static call
__ addl(counter_addr, DataLayout::counter_increment);
} }
} }
......
...@@ -1239,17 +1239,19 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register ...@@ -1239,17 +1239,19 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register
// If no method data exists, go to profile_continue. // If no method data exists, go to profile_continue.
test_method_data_pointer(mdp, profile_continue); test_method_data_pointer(mdp, profile_continue);
// We are making a call. Increment the count.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
Label skip_receiver_profile; Label skip_receiver_profile;
if (receiver_can_be_null) { if (receiver_can_be_null) {
Label not_null;
testptr(receiver, receiver); testptr(receiver, receiver);
jcc(Assembler::zero, skip_receiver_profile); jccb(Assembler::notZero, not_null);
// We are making a call. Increment the count for null receiver.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
jmp(skip_receiver_profile);
bind(not_null);
} }
// Record the receiver type. // Record the receiver type.
record_klass_in_profile(receiver, mdp, reg2); record_klass_in_profile(receiver, mdp, reg2, true);
bind(skip_receiver_profile); bind(skip_receiver_profile);
// The method data pointer needs to be updated to reflect the new target. // The method data pointer needs to be updated to reflect the new target.
...@@ -1263,10 +1265,14 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register ...@@ -1263,10 +1265,14 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register
void InterpreterMacroAssembler::record_klass_in_profile_helper( void InterpreterMacroAssembler::record_klass_in_profile_helper(
Register receiver, Register mdp, Register receiver, Register mdp,
Register reg2, Register reg2, int start_row,
int start_row, Label& done) { Label& done, bool is_virtual_call) {
if (TypeProfileWidth == 0) if (TypeProfileWidth == 0) {
if (is_virtual_call) {
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
}
return; return;
}
int last_row = VirtualCallData::row_limit() - 1; int last_row = VirtualCallData::row_limit() - 1;
assert(start_row <= last_row, "must be work left to do"); assert(start_row <= last_row, "must be work left to do");
...@@ -1294,19 +1300,28 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1294,19 +1300,28 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
bind(next_test); bind(next_test);
if (row == start_row) { if (row == start_row) {
Label found_null;
// Failed the equality check on receiver[n]... Test for null. // Failed the equality check on receiver[n]... Test for null.
testptr(reg2, reg2); testptr(reg2, reg2);
if (start_row == last_row) { if (start_row == last_row) {
// The only thing left to do is handle the null case. // The only thing left to do is handle the null case.
if (is_virtual_call) {
jccb(Assembler::zero, found_null);
// Receiver did not match any saved receiver and there is no empty row for it.
// Increment total counter to indicate polimorphic case.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
jmp(done);
bind(found_null);
} else {
jcc(Assembler::notZero, done); jcc(Assembler::notZero, done);
}
break; break;
} }
// Since null is rare, make it be the branch-taken case. // Since null is rare, make it be the branch-taken case.
Label found_null;
jcc(Assembler::zero, found_null); jcc(Assembler::zero, found_null);
// Put all the "Case 3" tests here. // Put all the "Case 3" tests here.
record_klass_in_profile_helper(receiver, mdp, reg2, start_row + 1, done); record_klass_in_profile_helper(receiver, mdp, reg2, start_row + 1, done, is_virtual_call);
// Found a null. Keep searching for a matching receiver, // Found a null. Keep searching for a matching receiver,
// but remember that this is an empty (unused) slot. // but remember that this is an empty (unused) slot.
...@@ -1323,16 +1338,18 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1323,16 +1338,18 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row)); int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row));
movptr(reg2, (int32_t)DataLayout::counter_increment); movptr(reg2, (int32_t)DataLayout::counter_increment);
set_mdp_data_at(mdp, count_offset, reg2); set_mdp_data_at(mdp, count_offset, reg2);
if (start_row > 0) {
jmp(done); jmp(done);
}
} }
void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, void InterpreterMacroAssembler::record_klass_in_profile(Register receiver,
Register mdp, Register mdp, Register reg2,
Register reg2) { bool is_virtual_call) {
assert(ProfileInterpreter, "must be profiling"); assert(ProfileInterpreter, "must be profiling");
Label done; Label done;
record_klass_in_profile_helper(receiver, mdp, reg2, 0, done); record_klass_in_profile_helper(receiver, mdp, reg2, 0, done, is_virtual_call);
bind (done); bind (done);
} }
...@@ -1425,7 +1442,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, ...@@ -1425,7 +1442,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass,
mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size());
// Record the object type. // Record the object type.
record_klass_in_profile(klass, mdp, reg2); record_klass_in_profile(klass, mdp, reg2, false);
assert(reg2 == rdi, "we know how to fix this blown reg"); assert(reg2 == rdi, "we know how to fix this blown reg");
restore_locals(); // Restore EDI restore_locals(); // Restore EDI
} }
......
...@@ -213,10 +213,10 @@ class InterpreterMacroAssembler: public MacroAssembler { ...@@ -213,10 +213,10 @@ class InterpreterMacroAssembler: public MacroAssembler {
Label& not_equal_continue); Label& not_equal_continue);
void record_klass_in_profile(Register receiver, Register mdp, void record_klass_in_profile(Register receiver, Register mdp,
Register reg2); Register reg2, bool is_virtual_call);
void record_klass_in_profile_helper(Register receiver, Register mdp, void record_klass_in_profile_helper(Register receiver, Register mdp,
Register reg2, Register reg2, int start_row,
int start_row, Label& done); Label& done, bool is_virtual_call);
void update_mdp_by_offset(Register mdp_in, int offset_of_offset); void update_mdp_by_offset(Register mdp_in, int offset_of_offset);
void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp); void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp);
......
...@@ -1262,17 +1262,19 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, ...@@ -1262,17 +1262,19 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver,
// If no method data exists, go to profile_continue. // If no method data exists, go to profile_continue.
test_method_data_pointer(mdp, profile_continue); test_method_data_pointer(mdp, profile_continue);
// We are making a call. Increment the count.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
Label skip_receiver_profile; Label skip_receiver_profile;
if (receiver_can_be_null) { if (receiver_can_be_null) {
Label not_null;
testptr(receiver, receiver); testptr(receiver, receiver);
jcc(Assembler::zero, skip_receiver_profile); jccb(Assembler::notZero, not_null);
// We are making a call. Increment the count for null receiver.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
jmp(skip_receiver_profile);
bind(not_null);
} }
// Record the receiver type. // Record the receiver type.
record_klass_in_profile(receiver, mdp, reg2); record_klass_in_profile(receiver, mdp, reg2, true);
bind(skip_receiver_profile); bind(skip_receiver_profile);
// The method data pointer needs to be updated to reflect the new target. // The method data pointer needs to be updated to reflect the new target.
...@@ -1296,10 +1298,14 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, ...@@ -1296,10 +1298,14 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver,
// See below for example code. // See below for example code.
void InterpreterMacroAssembler::record_klass_in_profile_helper( void InterpreterMacroAssembler::record_klass_in_profile_helper(
Register receiver, Register mdp, Register receiver, Register mdp,
Register reg2, Register reg2, int start_row,
int start_row, Label& done) { Label& done, bool is_virtual_call) {
if (TypeProfileWidth == 0) if (TypeProfileWidth == 0) {
if (is_virtual_call) {
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
}
return; return;
}
int last_row = VirtualCallData::row_limit() - 1; int last_row = VirtualCallData::row_limit() - 1;
assert(start_row <= last_row, "must be work left to do"); assert(start_row <= last_row, "must be work left to do");
...@@ -1327,19 +1333,28 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1327,19 +1333,28 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
bind(next_test); bind(next_test);
if (test_for_null_also) { if (test_for_null_also) {
Label found_null;
// Failed the equality check on receiver[n]... Test for null. // Failed the equality check on receiver[n]... Test for null.
testptr(reg2, reg2); testptr(reg2, reg2);
if (start_row == last_row) { if (start_row == last_row) {
// The only thing left to do is handle the null case. // The only thing left to do is handle the null case.
if (is_virtual_call) {
jccb(Assembler::zero, found_null);
// Receiver did not match any saved receiver and there is no empty row for it.
// Increment total counter to indicate polimorphic case.
increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset()));
jmp(done);
bind(found_null);
} else {
jcc(Assembler::notZero, done); jcc(Assembler::notZero, done);
}
break; break;
} }
// Since null is rare, make it be the branch-taken case. // Since null is rare, make it be the branch-taken case.
Label found_null;
jcc(Assembler::zero, found_null); jcc(Assembler::zero, found_null);
// Put all the "Case 3" tests here. // Put all the "Case 3" tests here.
record_klass_in_profile_helper(receiver, mdp, reg2, start_row + 1, done); record_klass_in_profile_helper(receiver, mdp, reg2, start_row + 1, done, is_virtual_call);
// Found a null. Keep searching for a matching receiver, // Found a null. Keep searching for a matching receiver,
// but remember that this is an empty (unused) slot. // but remember that this is an empty (unused) slot.
...@@ -1356,7 +1371,9 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1356,7 +1371,9 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row)); int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row));
movl(reg2, DataLayout::counter_increment); movl(reg2, DataLayout::counter_increment);
set_mdp_data_at(mdp, count_offset, reg2); set_mdp_data_at(mdp, count_offset, reg2);
if (start_row > 0) {
jmp(done); jmp(done);
}
} }
// Example state machine code for three profile rows: // Example state machine code for three profile rows:
...@@ -1368,7 +1385,7 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1368,7 +1385,7 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
// if (row[1].rec != NULL) { // if (row[1].rec != NULL) {
// // degenerate decision tree, rooted at row[2] // // degenerate decision tree, rooted at row[2]
// if (row[2].rec == rec) { row[2].incr(); goto done; } // if (row[2].rec == rec) { row[2].incr(); goto done; }
// if (row[2].rec != NULL) { goto done; } // overflow // if (row[2].rec != NULL) { count.incr(); goto done; } // overflow
// row[2].init(rec); goto done; // row[2].init(rec); goto done;
// } else { // } else {
// // remember row[1] is empty // // remember row[1] is empty
...@@ -1381,14 +1398,15 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper( ...@@ -1381,14 +1398,15 @@ void InterpreterMacroAssembler::record_klass_in_profile_helper(
// if (row[2].rec == rec) { row[2].incr(); goto done; } // if (row[2].rec == rec) { row[2].incr(); goto done; }
// row[0].init(rec); goto done; // row[0].init(rec); goto done;
// } // }
// done:
void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, void InterpreterMacroAssembler::record_klass_in_profile(Register receiver,
Register mdp, Register mdp, Register reg2,
Register reg2) { bool is_virtual_call) {
assert(ProfileInterpreter, "must be profiling"); assert(ProfileInterpreter, "must be profiling");
Label done; Label done;
record_klass_in_profile_helper(receiver, mdp, reg2, 0, done); record_klass_in_profile_helper(receiver, mdp, reg2, 0, done, is_virtual_call);
bind (done); bind (done);
} }
...@@ -1484,7 +1502,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, ...@@ -1484,7 +1502,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass,
mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size());
// Record the object type. // Record the object type.
record_klass_in_profile(klass, mdp, reg2); record_klass_in_profile(klass, mdp, reg2, false);
} }
update_mdp_by_constant(mdp, mdp_delta); update_mdp_by_constant(mdp, mdp_delta);
......
...@@ -222,10 +222,10 @@ class InterpreterMacroAssembler: public MacroAssembler { ...@@ -222,10 +222,10 @@ class InterpreterMacroAssembler: public MacroAssembler {
Label& not_equal_continue); Label& not_equal_continue);
void record_klass_in_profile(Register receiver, Register mdp, void record_klass_in_profile(Register receiver, Register mdp,
Register reg2); Register reg2, bool is_virtual_call);
void record_klass_in_profile_helper(Register receiver, Register mdp, void record_klass_in_profile_helper(Register receiver, Register mdp,
Register reg2, Register reg2, int start_row,
int start_row, Label& done); Label& done, bool is_virtual_call);
void update_mdp_by_offset(Register mdp_in, int offset_of_offset); void update_mdp_by_offset(Register mdp_in, int offset_of_offset);
void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp); void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp);
......
...@@ -436,15 +436,20 @@ ciCallProfile ciMethod::call_profile_at_bci(int bci) { ...@@ -436,15 +436,20 @@ ciCallProfile ciMethod::call_profile_at_bci(int bci) {
// we will set result._method also. // we will set result._method also.
} }
// Determine call site's morphism. // Determine call site's morphism.
// The call site count could be == (receivers_count_total + 1) // The call site count is 0 with known morphism (onlt 1 or 2 receivers)
// not only in the case of a polymorphic call but also in the case // or < 0 in the case of a type check failured for checkcast, aastore, instanceof.
// when a method data snapshot is taken after the site count was updated // The call site count is > 0 in the case of a polymorphic virtual call.
// but before receivers counters were updated. if (morphism > 0 && morphism == result._limit) {
if (morphism == result._limit) { // The morphism <= MorphismLimit.
// There were no array klasses and morphism <= MorphismLimit. if ((morphism < ciCallProfile::MorphismLimit) ||
if (morphism < ciCallProfile::MorphismLimit || (morphism == ciCallProfile::MorphismLimit && count == 0)) {
morphism == ciCallProfile::MorphismLimit && #ifdef ASSERT
(receivers_count_total+1) >= count) { if (count > 0) {
tty->print_cr("bci: %d", bci);
this->print_codes();
assert(false, "this call site should not be polymorphic");
}
#endif
result._morphism = morphism; result._morphism = morphism;
} }
} }
...@@ -452,10 +457,8 @@ ciCallProfile ciMethod::call_profile_at_bci(int bci) { ...@@ -452,10 +457,8 @@ ciCallProfile ciMethod::call_profile_at_bci(int bci) {
// zero or less, presume that this is a typecheck profile and // zero or less, presume that this is a typecheck profile and
// do nothing. Otherwise, increase count to be the sum of all // do nothing. Otherwise, increase count to be the sum of all
// receiver's counts. // receiver's counts.
if (count > 0) { if (count >= 0) {
if (count < receivers_count_total) { count += receivers_count_total;
count = receivers_count_total;
}
} }
} }
result._count = count; result._count = count;
......
...@@ -843,6 +843,7 @@ static bool count_find_witness_calls() { ...@@ -843,6 +843,7 @@ static bool count_find_witness_calls() {
if (occasional_print || final_stats) { if (occasional_print || final_stats) {
// Every now and then dump a little info about dependency searching. // Every now and then dump a little info about dependency searching.
if (xtty != NULL) { if (xtty != NULL) {
ttyLocker ttyl;
xtty->elem("deps_find_witness calls='%d' steps='%d' recursions='%d' singles='%d'", xtty->elem("deps_find_witness calls='%d' steps='%d' recursions='%d' singles='%d'",
deps_find_witness_calls, deps_find_witness_calls,
deps_find_witness_steps, deps_find_witness_steps,
...@@ -850,6 +851,7 @@ static bool count_find_witness_calls() { ...@@ -850,6 +851,7 @@ static bool count_find_witness_calls() {
deps_find_witness_singles); deps_find_witness_singles);
} }
if (final_stats || (TraceDependencies && WizardMode)) { if (final_stats || (TraceDependencies && WizardMode)) {
ttyLocker ttyl;
tty->print_cr("Dependency check (find_witness) " tty->print_cr("Dependency check (find_witness) "
"calls=%d, steps=%d (avg=%.1f), recursions=%d, singles=%d", "calls=%d, steps=%d (avg=%.1f), recursions=%d, singles=%d",
deps_find_witness_calls, deps_find_witness_calls,
......
...@@ -1117,7 +1117,6 @@ void nmethod::make_unloaded(BoolObjectClosure* is_alive, oop cause) { ...@@ -1117,7 +1117,6 @@ void nmethod::make_unloaded(BoolObjectClosure* is_alive, oop cause) {
if (_method->code() == this) { if (_method->code() == this) {
_method->clear_code(); // Break a cycle _method->clear_code(); // Break a cycle
} }
inc_decompile_count(); // Last chance to make a mark on the MDO
_method = NULL; // Clear the method of this dead nmethod _method = NULL; // Clear the method of this dead nmethod
} }
// Make the class unloaded - i.e., change state and notify sweeper // Make the class unloaded - i.e., change state and notify sweeper
...@@ -1177,15 +1176,17 @@ void nmethod::log_state_change() const { ...@@ -1177,15 +1176,17 @@ void nmethod::log_state_change() const {
bool nmethod::make_not_entrant_or_zombie(unsigned int state) { bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
assert(state == zombie || state == not_entrant, "must be zombie or not_entrant"); assert(state == zombie || state == not_entrant, "must be zombie or not_entrant");
// If the method is already zombie there is nothing to do bool was_alive = false;
if (is_zombie()) {
return false;
}
// Make sure the nmethod is not flushed in case of a safepoint in code below. // Make sure the nmethod is not flushed in case of a safepoint in code below.
nmethodLocker nml(this); nmethodLocker nml(this);
{ {
// If the method is already zombie there is nothing to do
if (is_zombie()) {
return false;
}
// invalidate osr nmethod before acquiring the patching lock since // invalidate osr nmethod before acquiring the patching lock since
// they both acquire leaf locks and we don't want a deadlock. // they both acquire leaf locks and we don't want a deadlock.
// This logic is equivalent to the logic below for patching the // This logic is equivalent to the logic below for patching the
...@@ -1223,6 +1224,8 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) { ...@@ -1223,6 +1224,8 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
assert(state == not_entrant, "other cases may need to be handled differently"); assert(state == not_entrant, "other cases may need to be handled differently");
} }
was_alive = is_in_use(); // Read state under lock
// Change state // Change state
flags.state = state; flags.state = state;
...@@ -1249,8 +1252,11 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) { ...@@ -1249,8 +1252,11 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) {
mark_as_seen_on_stack(); mark_as_seen_on_stack();
} }
if (was_alive) {
// It's a true state change, so mark the method as decompiled. // It's a true state change, so mark the method as decompiled.
// Do it only for transition from alive.
inc_decompile_count(); inc_decompile_count();
}
// zombie only - if a JVMTI agent has enabled the CompiledMethodUnload event // zombie only - if a JVMTI agent has enabled the CompiledMethodUnload event
// and it hasn't already been reported for this nmethod then report it now. // and it hasn't already been reported for this nmethod then report it now.
......
...@@ -1391,6 +1391,9 @@ public: ...@@ -1391,6 +1391,9 @@ public:
} }
void inc_decompile_count() { void inc_decompile_count() {
_nof_decompiles += 1; _nof_decompiles += 1;
if (decompile_count() > (uint)PerMethodRecompilationCutoff) {
method()->set_not_compilable();
}
} }
// Support for code generation // Support for code generation
......
...@@ -575,12 +575,6 @@ bool methodOopDesc::is_not_compilable(int comp_level) const { ...@@ -575,12 +575,6 @@ bool methodOopDesc::is_not_compilable(int comp_level) const {
return true; return true;
} }
methodDataOop mdo = method_data();
if (mdo != NULL
&& (uint)mdo->decompile_count() > (uint)PerMethodRecompilationCutoff) {
// Since (uint)-1 is large, -1 really means 'no cutoff'.
return true;
}
#ifdef COMPILER2 #ifdef COMPILER2
if (is_tier1_compile(comp_level)) { if (is_tier1_compile(comp_level)) {
if (is_not_tier1_compilable()) { if (is_not_tier1_compilable()) {
...@@ -594,6 +588,15 @@ bool methodOopDesc::is_not_compilable(int comp_level) const { ...@@ -594,6 +588,15 @@ bool methodOopDesc::is_not_compilable(int comp_level) const {
// call this when compiler finds that this method is not compilable // call this when compiler finds that this method is not compilable
void methodOopDesc::set_not_compilable(int comp_level) { void methodOopDesc::set_not_compilable(int comp_level) {
if (PrintCompilation) {
ttyLocker ttyl;
tty->print("made not compilable ");
this->print_short_name(tty);
int size = this->code_size();
if (size > 0)
tty->print(" (%d bytes)", size);
tty->cr();
}
if ((TraceDeoptimization || LogCompilation) && (xtty != NULL)) { if ((TraceDeoptimization || LogCompilation) && (xtty != NULL)) {
ttyLocker ttyl; ttyLocker ttyl;
xtty->begin_elem("make_not_compilable thread='%d'", (int) os::current_thread_id()); xtty->begin_elem("make_not_compilable thread='%d'", (int) os::current_thread_id());
......
...@@ -182,26 +182,16 @@ CallGenerator* Compile::call_generator(ciMethod* call_method, int vtable_index, ...@@ -182,26 +182,16 @@ CallGenerator* Compile::call_generator(ciMethod* call_method, int vtable_index,
} }
} }
CallGenerator* miss_cg; CallGenerator* miss_cg;
Deoptimization::DeoptReason reason = (profile.morphism() == 2) ?
Deoptimization::Reason_bimorphic :
Deoptimization::Reason_class_check;
if (( profile.morphism() == 1 || if (( profile.morphism() == 1 ||
(profile.morphism() == 2 && next_hit_cg != NULL) ) && (profile.morphism() == 2 && next_hit_cg != NULL) ) &&
!too_many_traps(jvms->method(), jvms->bci(), reason)
!too_many_traps(Deoptimization::Reason_class_check)
// Check only total number of traps per method to allow
// the transition from monomorphic to bimorphic case between
// compilations without falling into virtual call.
// A monomorphic case may have the class_check trap flag is set
// due to the time gap between the uncommon trap processing
// when flags are set in MDO and the call site bytecode execution
// in Interpreter when MDO counters are updated.
// There was also class_check trap in monomorphic case due to
// the bug 6225440.
) { ) {
// Generate uncommon trap for class check failure path // Generate uncommon trap for class check failure path
// in case of monomorphic or bimorphic virtual call site. // in case of monomorphic or bimorphic virtual call site.
miss_cg = CallGenerator::for_uncommon_trap(call_method, miss_cg = CallGenerator::for_uncommon_trap(call_method, reason,
Deoptimization::Reason_class_check,
Deoptimization::Action_maybe_recompile); Deoptimization::Action_maybe_recompile);
} else { } else {
// Generate virtual call for class check failure path // Generate virtual call for class check failure path
......
...@@ -414,8 +414,6 @@ void Parse::profile_not_taken_branch(bool force_update) { ...@@ -414,8 +414,6 @@ void Parse::profile_not_taken_branch(bool force_update) {
void Parse::profile_call(Node* receiver) { void Parse::profile_call(Node* receiver) {
if (!method_data_update()) return; if (!method_data_update()) return;
profile_generic_call();
switch (bc()) { switch (bc()) {
case Bytecodes::_invokevirtual: case Bytecodes::_invokevirtual:
case Bytecodes::_invokeinterface: case Bytecodes::_invokeinterface:
...@@ -424,6 +422,7 @@ void Parse::profile_call(Node* receiver) { ...@@ -424,6 +422,7 @@ void Parse::profile_call(Node* receiver) {
case Bytecodes::_invokestatic: case Bytecodes::_invokestatic:
case Bytecodes::_invokedynamic: case Bytecodes::_invokedynamic:
case Bytecodes::_invokespecial: case Bytecodes::_invokespecial:
profile_generic_call();
break; break;
default: fatal("unexpected call bytecode"); default: fatal("unexpected call bytecode");
} }
...@@ -444,13 +443,16 @@ void Parse::profile_generic_call() { ...@@ -444,13 +443,16 @@ void Parse::profile_generic_call() {
void Parse::profile_receiver_type(Node* receiver) { void Parse::profile_receiver_type(Node* receiver) {
assert(method_data_update(), "must be generating profile code"); assert(method_data_update(), "must be generating profile code");
// Skip if we aren't tracking receivers
if (TypeProfileWidth < 1) return;
ciMethodData* md = method()->method_data(); ciMethodData* md = method()->method_data();
assert(md != NULL, "expected valid ciMethodData"); assert(md != NULL, "expected valid ciMethodData");
ciProfileData* data = md->bci_to_data(bci()); ciProfileData* data = md->bci_to_data(bci());
assert(data->is_ReceiverTypeData(), "need ReceiverTypeData here"); assert(data->is_ReceiverTypeData(), "need ReceiverTypeData here");
// Skip if we aren't tracking receivers
if (TypeProfileWidth < 1) {
increment_md_counter_at(md, data, CounterData::count_offset());
return;
}
ciReceiverTypeData* rdata = (ciReceiverTypeData*)data->as_ReceiverTypeData(); ciReceiverTypeData* rdata = (ciReceiverTypeData*)data->as_ReceiverTypeData();
Node* method_data = method_data_addressing(md, rdata, in_ByteSize(0)); Node* method_data = method_data_addressing(md, rdata, in_ByteSize(0));
......
...@@ -706,6 +706,11 @@ JRT_LEAF(void, OptoRuntime::profile_receiver_type_C(DataLayout* data, oopDesc* r ...@@ -706,6 +706,11 @@ JRT_LEAF(void, OptoRuntime::profile_receiver_type_C(DataLayout* data, oopDesc* r
// vc->set_receiver_count(empty_row, DataLayout::counter_increment); // vc->set_receiver_count(empty_row, DataLayout::counter_increment);
int count_off = ReceiverTypeData::receiver_count_cell_index(empty_row); int count_off = ReceiverTypeData::receiver_count_cell_index(empty_row);
*(mdp + count_off) = DataLayout::counter_increment; *(mdp + count_off) = DataLayout::counter_increment;
} else {
// Receiver did not match any saved receiver and there is no empty row for it.
// Increment total counter to indicate polimorphic case.
intptr_t* count_p = (intptr_t*)(((byte*)(data)) + in_bytes(CounterData::count_offset()));
*count_p += DataLayout::counter_increment;
} }
JRT_END JRT_END
......
...@@ -1338,13 +1338,14 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra ...@@ -1338,13 +1338,14 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra
// Whether the interpreter is producing MDO data or not, we also need // Whether the interpreter is producing MDO data or not, we also need
// to use the MDO to detect hot deoptimization points and control // to use the MDO to detect hot deoptimization points and control
// aggressive optimization. // aggressive optimization.
bool inc_recompile_count = false;
ProfileData* pdata = NULL;
if (ProfileTraps && update_trap_state && trap_mdo.not_null()) { if (ProfileTraps && update_trap_state && trap_mdo.not_null()) {
assert(trap_mdo() == get_method_data(thread, trap_method, false), "sanity"); assert(trap_mdo() == get_method_data(thread, trap_method, false), "sanity");
uint this_trap_count = 0; uint this_trap_count = 0;
bool maybe_prior_trap = false; bool maybe_prior_trap = false;
bool maybe_prior_recompile = false; bool maybe_prior_recompile = false;
ProfileData* pdata pdata = query_update_method_data(trap_mdo, trap_bci, reason,
= query_update_method_data(trap_mdo, trap_bci, reason,
//outputs: //outputs:
this_trap_count, this_trap_count,
maybe_prior_trap, maybe_prior_trap,
...@@ -1380,18 +1381,7 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra ...@@ -1380,18 +1381,7 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra
// Detect repeated recompilation at the same BCI, and enforce a limit. // Detect repeated recompilation at the same BCI, and enforce a limit.
if (make_not_entrant && maybe_prior_recompile) { if (make_not_entrant && maybe_prior_recompile) {
// More than one recompile at this point. // More than one recompile at this point.
trap_mdo->inc_overflow_recompile_count(); inc_recompile_count = maybe_prior_trap;
if (maybe_prior_trap
&& ((uint)trap_mdo->overflow_recompile_count()
> (uint)PerBytecodeRecompilationCutoff)) {
// Give up on the method containing the bad BCI.
if (trap_method() == nm->method()) {
make_not_compilable = true;
} else {
trap_method->set_not_compilable();
// But give grace to the enclosing nm->method().
}
}
} }
} else { } else {
// For reasons which are not recorded per-bytecode, we simply // For reasons which are not recorded per-bytecode, we simply
...@@ -1418,7 +1408,17 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra ...@@ -1418,7 +1408,17 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra
reset_counters = true; reset_counters = true;
} }
if (make_not_entrant && pdata != NULL) { }
// Take requested actions on the method:
// Recompile
if (make_not_entrant) {
if (!nm->make_not_entrant()) {
return; // the call did not change nmethod's state
}
if (pdata != NULL) {
// Record the recompilation event, if any. // Record the recompilation event, if any.
int tstate0 = pdata->trap_state(); int tstate0 = pdata->trap_state();
int tstate1 = trap_state_set_recompiled(tstate0, true); int tstate1 = trap_state_set_recompiled(tstate0, true);
...@@ -1427,7 +1427,19 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra ...@@ -1427,7 +1427,19 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra
} }
} }
// Take requested actions on the method: if (inc_recompile_count) {
trap_mdo->inc_overflow_recompile_count();
if ((uint)trap_mdo->overflow_recompile_count() >
(uint)PerBytecodeRecompilationCutoff) {
// Give up on the method containing the bad BCI.
if (trap_method() == nm->method()) {
make_not_compilable = true;
} else {
trap_method->set_not_compilable();
// But give grace to the enclosing nm->method().
}
}
}
// Reset invocation counters // Reset invocation counters
if (reset_counters) { if (reset_counters) {
...@@ -1437,13 +1449,8 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra ...@@ -1437,13 +1449,8 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* thread, jint tra
reset_invocation_counter(trap_scope); reset_invocation_counter(trap_scope);
} }
// Recompile
if (make_not_entrant) {
nm->make_not_entrant();
}
// Give up compiling // Give up compiling
if (make_not_compilable) { if (make_not_compilable && !nm->method()->is_not_compilable()) {
assert(make_not_entrant, "consistent"); assert(make_not_entrant, "consistent");
nm->method()->set_not_compilable(); nm->method()->set_not_compilable();
} }
...@@ -1516,11 +1523,13 @@ Deoptimization::query_update_method_data(methodDataHandle trap_mdo, ...@@ -1516,11 +1523,13 @@ Deoptimization::query_update_method_data(methodDataHandle trap_mdo,
if (tstate1 != tstate0) if (tstate1 != tstate0)
pdata->set_trap_state(tstate1); pdata->set_trap_state(tstate1);
} else { } else {
if (LogCompilation && xtty != NULL) if (LogCompilation && xtty != NULL) {
ttyLocker ttyl;
// Missing MDP? Leave a small complaint in the log. // Missing MDP? Leave a small complaint in the log.
xtty->elem("missing_mdp bci='%d'", trap_bci); xtty->elem("missing_mdp bci='%d'", trap_bci);
} }
} }
}
// Return results: // Return results:
ret_this_trap_count = this_trap_count; ret_this_trap_count = this_trap_count;
...@@ -1672,6 +1681,7 @@ const char* Deoptimization::_trap_reason_name[Reason_LIMIT] = { ...@@ -1672,6 +1681,7 @@ const char* Deoptimization::_trap_reason_name[Reason_LIMIT] = {
"class_check", "class_check",
"array_check", "array_check",
"intrinsic", "intrinsic",
"bimorphic",
"unloaded", "unloaded",
"uninitialized", "uninitialized",
"unreached", "unreached",
......
...@@ -33,12 +33,15 @@ class Deoptimization : AllStatic { ...@@ -33,12 +33,15 @@ class Deoptimization : AllStatic {
enum DeoptReason { enum DeoptReason {
Reason_many = -1, // indicates presence of several reasons Reason_many = -1, // indicates presence of several reasons
Reason_none = 0, // indicates absence of a relevant deopt. Reason_none = 0, // indicates absence of a relevant deopt.
// Next 7 reasons are recorded per bytecode in DataLayout::trap_bits
Reason_null_check, // saw unexpected null or zero divisor (@bci) Reason_null_check, // saw unexpected null or zero divisor (@bci)
Reason_null_assert, // saw unexpected non-null or non-zero (@bci) Reason_null_assert, // saw unexpected non-null or non-zero (@bci)
Reason_range_check, // saw unexpected array index (@bci) Reason_range_check, // saw unexpected array index (@bci)
Reason_class_check, // saw unexpected object class (@bci) Reason_class_check, // saw unexpected object class (@bci)
Reason_array_check, // saw unexpected array class (aastore @bci) Reason_array_check, // saw unexpected array class (aastore @bci)
Reason_intrinsic, // saw unexpected operand to intrinsic (@bci) Reason_intrinsic, // saw unexpected operand to intrinsic (@bci)
Reason_bimorphic, // saw unexpected object class in bimorphic inlining (@bci)
Reason_unloaded, // unloaded class or constant pool entry Reason_unloaded, // unloaded class or constant pool entry
Reason_uninitialized, // bad class state (uninitialized) Reason_uninitialized, // bad class state (uninitialized)
Reason_unreached, // code is not reached, compiler Reason_unreached, // code is not reached, compiler
...@@ -49,7 +52,7 @@ class Deoptimization : AllStatic { ...@@ -49,7 +52,7 @@ class Deoptimization : AllStatic {
Reason_predicate, // compiler generated predicate failed Reason_predicate, // compiler generated predicate failed
Reason_LIMIT, Reason_LIMIT,
// Note: Keep this enum in sync. with _trap_reason_name. // Note: Keep this enum in sync. with _trap_reason_name.
Reason_RECORDED_LIMIT = Reason_unloaded // some are not recorded per bc Reason_RECORDED_LIMIT = Reason_bimorphic // some are not recorded per bc
// Note: Reason_RECORDED_LIMIT should be < 8 to fit into 3 bits of // Note: Reason_RECORDED_LIMIT should be < 8 to fit into 3 bits of
// DataLayout::trap_bits. This dependency is enforced indirectly // DataLayout::trap_bits. This dependency is enforced indirectly
// via asserts, to avoid excessive direct header-to-header dependencies. // via asserts, to avoid excessive direct header-to-header dependencies.
...@@ -279,7 +282,7 @@ class Deoptimization : AllStatic { ...@@ -279,7 +282,7 @@ class Deoptimization : AllStatic {
int trap_state); int trap_state);
static bool reason_is_recorded_per_bytecode(DeoptReason reason) { static bool reason_is_recorded_per_bytecode(DeoptReason reason) {
return reason > Reason_none && reason < Reason_RECORDED_LIMIT; return reason > Reason_none && reason <= Reason_RECORDED_LIMIT;
} }
static DeoptReason reason_recorded_per_bytecode_if_any(DeoptReason reason) { static DeoptReason reason_recorded_per_bytecode_if_any(DeoptReason reason) {
......
...@@ -2864,7 +2864,7 @@ class CommandLineFlags { ...@@ -2864,7 +2864,7 @@ class CommandLineFlags {
product(intx, PerMethodRecompilationCutoff, 400, \ product(intx, PerMethodRecompilationCutoff, 400, \
"After recompiling N times, stay in the interpreter (-1=>'Inf')") \ "After recompiling N times, stay in the interpreter (-1=>'Inf')") \
\ \
product(intx, PerBytecodeRecompilationCutoff, 100, \ product(intx, PerBytecodeRecompilationCutoff, 200, \
"Per-BCI limit on repeated recompilation (-1=>'Inf')") \ "Per-BCI limit on repeated recompilation (-1=>'Inf')") \
\ \
product(intx, PerMethodTrapLimit, 100, \ product(intx, PerMethodTrapLimit, 100, \
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册