提交 158ed41f 编写于 作者: N never

6849984: Value methods for platform dependent math functions constant fold incorrectly

Reviewed-by: kvn, twisti
上级 42263eca
/* /*
* Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -394,6 +394,11 @@ address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter: ...@@ -394,6 +394,11 @@ address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter:
} }
bool AbstractInterpreter::can_be_compiled(methodHandle m) {
// No special entry points that preclude compilation
return true;
}
// This method tells the deoptimizer how big an interpreted frame must be: // This method tells the deoptimizer how big an interpreted frame must be:
int AbstractInterpreter::size_activation(methodOop method, int AbstractInterpreter::size_activation(methodOop method,
int tempcount, int tempcount,
......
/* /*
* Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -2862,6 +2862,9 @@ class StubGenerator: public StubCodeGenerator { ...@@ -2862,6 +2862,9 @@ class StubGenerator: public StubCodeGenerator {
// arraycopy stubs used by compilers // arraycopy stubs used by compilers
generate_arraycopy_stubs(); generate_arraycopy_stubs();
// Don't initialize the platform math functions since sparc
// doesn't have intrinsics for these operations.
} }
......
/* /*
* Copyright 1999-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1999-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -2030,6 +2030,54 @@ class StubGenerator: public StubCodeGenerator { ...@@ -2030,6 +2030,54 @@ class StubGenerator: public StubCodeGenerator {
entry_checkcast_arraycopy); entry_checkcast_arraycopy);
} }
void generate_math_stubs() {
{
StubCodeMark mark(this, "StubRoutines", "log");
StubRoutines::_intrinsic_log = (double (*)(double)) __ pc();
__ fld_d(Address(rsp, 4));
__ flog();
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "log10");
StubRoutines::_intrinsic_log10 = (double (*)(double)) __ pc();
__ fld_d(Address(rsp, 4));
__ flog10();
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "sin");
StubRoutines::_intrinsic_sin = (double (*)(double)) __ pc();
__ fld_d(Address(rsp, 4));
__ trigfunc('s');
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "cos");
StubRoutines::_intrinsic_cos = (double (*)(double)) __ pc();
__ fld_d(Address(rsp, 4));
__ trigfunc('c');
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "tan");
StubRoutines::_intrinsic_tan = (double (*)(double)) __ pc();
__ fld_d(Address(rsp, 4));
__ trigfunc('t');
__ ret(0);
}
// The intrinsic version of these seem to return the same value as
// the strict version.
StubRoutines::_intrinsic_exp = SharedRuntime::dexp;
StubRoutines::_intrinsic_pow = SharedRuntime::dpow;
}
public: public:
// Information about frame layout at time of blocking runtime call. // Information about frame layout at time of blocking runtime call.
// Note that we only have to preserve callee-saved registers since // Note that we only have to preserve callee-saved registers since
...@@ -2228,6 +2276,8 @@ class StubGenerator: public StubCodeGenerator { ...@@ -2228,6 +2276,8 @@ class StubGenerator: public StubCodeGenerator {
MethodHandles::generate_method_handle_stub(_masm, ek); MethodHandles::generate_method_handle_stub(_masm, ek);
} }
} }
generate_math_stubs();
} }
......
/* /*
* Copyright 2003-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 2003-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -2731,6 +2731,79 @@ class StubGenerator: public StubCodeGenerator { ...@@ -2731,6 +2731,79 @@ class StubGenerator: public StubCodeGenerator {
StubRoutines::_arrayof_oop_arraycopy = StubRoutines::_oop_arraycopy; StubRoutines::_arrayof_oop_arraycopy = StubRoutines::_oop_arraycopy;
} }
void generate_math_stubs() {
{
StubCodeMark mark(this, "StubRoutines", "log");
StubRoutines::_intrinsic_log = (double (*)(double)) __ pc();
__ subq(rsp, 8);
__ movdbl(Address(rsp, 0), xmm0);
__ fld_d(Address(rsp, 0));
__ flog();
__ fstp_d(Address(rsp, 0));
__ movdbl(xmm0, Address(rsp, 0));
__ addq(rsp, 8);
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "log10");
StubRoutines::_intrinsic_log10 = (double (*)(double)) __ pc();
__ subq(rsp, 8);
__ movdbl(Address(rsp, 0), xmm0);
__ fld_d(Address(rsp, 0));
__ flog10();
__ fstp_d(Address(rsp, 0));
__ movdbl(xmm0, Address(rsp, 0));
__ addq(rsp, 8);
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "sin");
StubRoutines::_intrinsic_sin = (double (*)(double)) __ pc();
__ subq(rsp, 8);
__ movdbl(Address(rsp, 0), xmm0);
__ fld_d(Address(rsp, 0));
__ trigfunc('s');
__ fstp_d(Address(rsp, 0));
__ movdbl(xmm0, Address(rsp, 0));
__ addq(rsp, 8);
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "cos");
StubRoutines::_intrinsic_cos = (double (*)(double)) __ pc();
__ subq(rsp, 8);
__ movdbl(Address(rsp, 0), xmm0);
__ fld_d(Address(rsp, 0));
__ trigfunc('c');
__ fstp_d(Address(rsp, 0));
__ movdbl(xmm0, Address(rsp, 0));
__ addq(rsp, 8);
__ ret(0);
}
{
StubCodeMark mark(this, "StubRoutines", "tan");
StubRoutines::_intrinsic_tan = (double (*)(double)) __ pc();
__ subq(rsp, 8);
__ movdbl(Address(rsp, 0), xmm0);
__ fld_d(Address(rsp, 0));
__ trigfunc('t');
__ fstp_d(Address(rsp, 0));
__ movdbl(xmm0, Address(rsp, 0));
__ addq(rsp, 8);
__ ret(0);
}
// The intrinsic version of these seem to return the same value as
// the strict version.
StubRoutines::_intrinsic_exp = SharedRuntime::dexp;
StubRoutines::_intrinsic_pow = SharedRuntime::dpow;
}
#undef __ #undef __
#define __ masm-> #define __ masm->
...@@ -2945,6 +3018,8 @@ class StubGenerator: public StubCodeGenerator { ...@@ -2945,6 +3018,8 @@ class StubGenerator: public StubCodeGenerator {
MethodHandles::generate_method_handle_stub(_masm, ek); MethodHandles::generate_method_handle_stub(_masm, ek);
} }
} }
generate_math_stubs();
} }
public: public:
......
/* /*
* Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -1431,6 +1431,23 @@ address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter: ...@@ -1431,6 +1431,23 @@ address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter:
} }
// These should never be compiled since the interpreter will prefer
// the compiled version to the intrinsic version.
bool AbstractInterpreter::can_be_compiled(methodHandle m) {
switch (method_kind(m)) {
case Interpreter::java_lang_math_sin : // fall thru
case Interpreter::java_lang_math_cos : // fall thru
case Interpreter::java_lang_math_tan : // fall thru
case Interpreter::java_lang_math_abs : // fall thru
case Interpreter::java_lang_math_log : // fall thru
case Interpreter::java_lang_math_log10 : // fall thru
case Interpreter::java_lang_math_sqrt :
return false;
default:
return true;
}
}
// How much stack a method activation needs in words. // How much stack a method activation needs in words.
int AbstractInterpreter::size_top_interpreter_activation(methodOop method) { int AbstractInterpreter::size_top_interpreter_activation(methodOop method) {
......
/* /*
* Copyright 2003-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 2003-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -1456,6 +1456,23 @@ address AbstractInterpreterGenerator::generate_method_entry( ...@@ -1456,6 +1456,23 @@ address AbstractInterpreterGenerator::generate_method_entry(
generate_normal_entry(synchronized); generate_normal_entry(synchronized);
} }
// These should never be compiled since the interpreter will prefer
// the compiled version to the intrinsic version.
bool AbstractInterpreter::can_be_compiled(methodHandle m) {
switch (method_kind(m)) {
case Interpreter::java_lang_math_sin : // fall thru
case Interpreter::java_lang_math_cos : // fall thru
case Interpreter::java_lang_math_tan : // fall thru
case Interpreter::java_lang_math_abs : // fall thru
case Interpreter::java_lang_math_log : // fall thru
case Interpreter::java_lang_math_log10 : // fall thru
case Interpreter::java_lang_math_sqrt :
return false;
default:
return true;
}
}
// How much stack a method activation needs in words. // How much stack a method activation needs in words.
int AbstractInterpreter::size_top_interpreter_activation(methodOop method) { int AbstractInterpreter::size_top_interpreter_activation(methodOop method) {
const int entry_size = frame::interpreter_frame_monitor_size(); const int entry_size = frame::interpreter_frame_monitor_size();
......
/* /*
* Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -109,6 +109,8 @@ class AbstractInterpreter: AllStatic { ...@@ -109,6 +109,8 @@ class AbstractInterpreter: AllStatic {
static void print_method_kind(MethodKind kind) PRODUCT_RETURN; static void print_method_kind(MethodKind kind) PRODUCT_RETURN;
static bool can_be_compiled(methodHandle m);
// Runtime support // Runtime support
// length = invoke bytecode length (to advance to next bytecode) // length = invoke bytecode length (to advance to next bytecode)
......
/* /*
* Copyright 1997-2009 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -1244,8 +1244,7 @@ const Type *CosDNode::Value( PhaseTransform *phase ) const { ...@@ -1244,8 +1244,7 @@ const Type *CosDNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_cos( d ) );
return TypeD::make( SharedRuntime::dcos( d ) );
} }
//============================================================================= //=============================================================================
...@@ -1256,8 +1255,7 @@ const Type *SinDNode::Value( PhaseTransform *phase ) const { ...@@ -1256,8 +1255,7 @@ const Type *SinDNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_sin( d ) );
return TypeD::make( SharedRuntime::dsin( d ) );
} }
//============================================================================= //=============================================================================
...@@ -1268,8 +1266,7 @@ const Type *TanDNode::Value( PhaseTransform *phase ) const { ...@@ -1268,8 +1266,7 @@ const Type *TanDNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_tan( d ) );
return TypeD::make( SharedRuntime::dtan( d ) );
} }
//============================================================================= //=============================================================================
...@@ -1280,8 +1277,7 @@ const Type *LogDNode::Value( PhaseTransform *phase ) const { ...@@ -1280,8 +1277,7 @@ const Type *LogDNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_log( d ) );
return TypeD::make( SharedRuntime::dlog( d ) );
} }
//============================================================================= //=============================================================================
...@@ -1292,8 +1288,7 @@ const Type *Log10DNode::Value( PhaseTransform *phase ) const { ...@@ -1292,8 +1288,7 @@ const Type *Log10DNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_log10( d ) );
return TypeD::make( SharedRuntime::dlog10( d ) );
} }
//============================================================================= //=============================================================================
...@@ -1304,8 +1299,7 @@ const Type *ExpDNode::Value( PhaseTransform *phase ) const { ...@@ -1304,8 +1299,7 @@ const Type *ExpDNode::Value( PhaseTransform *phase ) const {
if( t1 == Type::TOP ) return Type::TOP; if( t1 == Type::TOP ) return Type::TOP;
if( t1->base() != Type::DoubleCon ) return Type::DOUBLE; if( t1->base() != Type::DoubleCon ) return Type::DOUBLE;
double d = t1->getd(); double d = t1->getd();
if( d < 0.0 ) return Type::DOUBLE; return TypeD::make( StubRoutines::intrinsic_exp( d ) );
return TypeD::make( SharedRuntime::dexp( d ) );
} }
...@@ -1323,5 +1317,5 @@ const Type *PowDNode::Value( PhaseTransform *phase ) const { ...@@ -1323,5 +1317,5 @@ const Type *PowDNode::Value( PhaseTransform *phase ) const {
double d2 = t2->getd(); double d2 = t2->getd();
if( d1 < 0.0 ) return Type::DOUBLE; if( d1 < 0.0 ) return Type::DOUBLE;
if( d2 < 0.0 ) return Type::DOUBLE; if( d2 < 0.0 ) return Type::DOUBLE;
return TypeD::make( SharedRuntime::dpow( d1, d2 ) ); return TypeD::make( StubRoutines::intrinsic_pow( d1, d2 ) );
} }
/* /*
* Copyright 2000-2007 Sun Microsystems, Inc. All Rights Reserved. * Copyright 2000-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -74,6 +74,16 @@ bool CompilationPolicy::canBeCompiled(methodHandle m) { ...@@ -74,6 +74,16 @@ bool CompilationPolicy::canBeCompiled(methodHandle m) {
if (m->is_abstract()) return false; if (m->is_abstract()) return false;
if (DontCompileHugeMethods && m->code_size() > HugeMethodLimit) return false; if (DontCompileHugeMethods && m->code_size() > HugeMethodLimit) return false;
// Math intrinsics should never be compiled as this can lead to
// monotonicity problems because the interpreter will prefer the
// compiled code to the intrinsic version. This can't happen in
// production because the invocation counter can't be incremented
// but we shouldn't expose the system to this problem in testing
// modes.
if (!AbstractInterpreter::can_be_compiled(m)) {
return false;
}
return !m->is_not_compilable(); return !m->is_not_compilable();
} }
......
/* /*
* Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -97,6 +97,14 @@ address StubRoutines::_checkcast_arraycopy = NULL; ...@@ -97,6 +97,14 @@ address StubRoutines::_checkcast_arraycopy = NULL;
address StubRoutines::_unsafe_arraycopy = NULL; address StubRoutines::_unsafe_arraycopy = NULL;
address StubRoutines::_generic_arraycopy = NULL; address StubRoutines::_generic_arraycopy = NULL;
double (* StubRoutines::_intrinsic_log )(double) = NULL;
double (* StubRoutines::_intrinsic_log10 )(double) = NULL;
double (* StubRoutines::_intrinsic_exp )(double) = NULL;
double (* StubRoutines::_intrinsic_pow )(double, double) = NULL;
double (* StubRoutines::_intrinsic_sin )(double) = NULL;
double (* StubRoutines::_intrinsic_cos )(double) = NULL;
double (* StubRoutines::_intrinsic_tan )(double) = NULL;
// Initialization // Initialization
// //
// Note: to break cycle with universe initialization, stubs are generated in two phases. // Note: to break cycle with universe initialization, stubs are generated in two phases.
......
/* /*
* Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1997-2010 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -148,6 +148,20 @@ class StubRoutines: AllStatic { ...@@ -148,6 +148,20 @@ class StubRoutines: AllStatic {
static address _unsafe_arraycopy; static address _unsafe_arraycopy;
static address _generic_arraycopy; static address _generic_arraycopy;
// These are versions of the java.lang.Math methods which perform
// the same operations as the intrinsic version. They are used for
// constant folding in the compiler to ensure equivalence. If the
// intrinsic version returns the same result as the strict version
// then they can be set to the appropriate function from
// SharedRuntime.
static double (*_intrinsic_log)(double);
static double (*_intrinsic_log10)(double);
static double (*_intrinsic_exp)(double);
static double (*_intrinsic_pow)(double, double);
static double (*_intrinsic_sin)(double);
static double (*_intrinsic_cos)(double);
static double (*_intrinsic_tan)(double);
public: public:
// Initialization/Testing // Initialization/Testing
static void initialize1(); // must happen before universe::genesis static void initialize1(); // must happen before universe::genesis
...@@ -245,6 +259,35 @@ class StubRoutines: AllStatic { ...@@ -245,6 +259,35 @@ class StubRoutines: AllStatic {
static address unsafe_arraycopy() { return _unsafe_arraycopy; } static address unsafe_arraycopy() { return _unsafe_arraycopy; }
static address generic_arraycopy() { return _generic_arraycopy; } static address generic_arraycopy() { return _generic_arraycopy; }
static double intrinsic_log(double d) {
assert(_intrinsic_log != NULL, "must be defined");
return _intrinsic_log(d);
}
static double intrinsic_log10(double d) {
assert(_intrinsic_log != NULL, "must be defined");
return _intrinsic_log10(d);
}
static double intrinsic_exp(double d) {
assert(_intrinsic_exp != NULL, "must be defined");
return _intrinsic_exp(d);
}
static double intrinsic_pow(double d, double d2) {
assert(_intrinsic_pow != NULL, "must be defined");
return _intrinsic_pow(d, d2);
}
static double intrinsic_sin(double d) {
assert(_intrinsic_sin != NULL, "must be defined");
return _intrinsic_sin(d);
}
static double intrinsic_cos(double d) {
assert(_intrinsic_cos != NULL, "must be defined");
return _intrinsic_cos(d);
}
static double intrinsic_tan(double d) {
assert(_intrinsic_tan != NULL, "must be defined");
return _intrinsic_tan(d);
}
// //
// Default versions of the above arraycopy functions for platforms which do // Default versions of the above arraycopy functions for platforms which do
// not have specialized versions // not have specialized versions
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册