Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
gaoqiong
MIGraphX
Commits
eb0d8fee
Commit
eb0d8fee
authored
Jun 04, 2019
by
Paul
Browse files
Merge branch 'develop' into driver
parents
65ef35cd
0d796941
Changes
320
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
482 additions
and
30 deletions
+482
-30
src/include/migraphx/check_shapes.hpp
src/include/migraphx/check_shapes.hpp
+19
-0
src/include/migraphx/concat_opt.hpp
src/include/migraphx/concat_opt.hpp
+1
-1
src/include/migraphx/eliminate_identity.hpp
src/include/migraphx/eliminate_identity.hpp
+27
-0
src/include/migraphx/eliminate_pad.hpp
src/include/migraphx/eliminate_pad.hpp
+30
-0
src/include/migraphx/env.hpp
src/include/migraphx/env.hpp
+9
-0
src/include/migraphx/functional.hpp
src/include/migraphx/functional.hpp
+12
-0
src/include/migraphx/generate.hpp
src/include/migraphx/generate.hpp
+1
-1
src/include/migraphx/instruction.hpp
src/include/migraphx/instruction.hpp
+6
-3
src/include/migraphx/int_divide.hpp
src/include/migraphx/int_divide.hpp
+25
-0
src/include/migraphx/iterator_for.hpp
src/include/migraphx/iterator_for.hpp
+54
-5
src/include/migraphx/literal.hpp
src/include/migraphx/literal.hpp
+2
-2
src/include/migraphx/make_signed.hpp
src/include/migraphx/make_signed.hpp
+22
-0
src/include/migraphx/onnx.hpp
src/include/migraphx/onnx.hpp
+0
-18
src/include/migraphx/op/abnormal_ops.hpp
src/include/migraphx/op/abnormal_ops.hpp
+67
-0
src/include/migraphx/op/abs.hpp
src/include/migraphx/op/abs.hpp
+33
-0
src/include/migraphx/op/acos.hpp
src/include/migraphx/op/acos.hpp
+32
-0
src/include/migraphx/op/add.hpp
src/include/migraphx/op/add.hpp
+32
-0
src/include/migraphx/op/as_shape.hpp
src/include/migraphx/op/as_shape.hpp
+46
-0
src/include/migraphx/op/asin.hpp
src/include/migraphx/op/asin.hpp
+32
-0
src/include/migraphx/op/atan.hpp
src/include/migraphx/op/atan.hpp
+32
-0
No files found.
src/include/migraphx/check_shapes.hpp
View file @
eb0d8fee
...
...
@@ -18,6 +18,11 @@ struct check_shapes
{
}
template
<
class
Op
>
check_shapes
(
const
shape
*
b
,
const
shape
*
e
,
const
Op
&
op
)
:
begin
(
b
),
end
(
e
),
name
(
op
.
name
())
{
}
check_shapes
(
const
std
::
vector
<
shape
>&
s
)
:
begin
(
s
.
data
()),
end
(
s
.
data
()
+
s
.
size
())
{}
template
<
class
Op
>
...
...
@@ -98,6 +103,13 @@ struct check_shapes
return
*
this
;
}
const
check_shapes
&
standard_or_scalar
()
const
{
if
(
!
this
->
all_of
([](
const
shape
&
s
)
{
return
s
.
standard
()
or
s
.
scalar
();
}))
MIGRAPHX_THROW
(
prefix
()
+
"Shapes are not a scalar or in standard layout"
);
return
*
this
;
}
const
check_shapes
&
packed
()
const
{
if
(
!
this
->
all_of
([](
const
shape
&
s
)
{
return
s
.
packed
();
}))
...
...
@@ -119,6 +131,13 @@ struct check_shapes
return
*
this
;
}
const
check_shapes
&
elements
(
std
::
size_t
n
)
const
{
if
(
!
this
->
all_of
([
&
](
const
shape
&
s
)
{
return
s
.
elements
()
==
n
;
}))
MIGRAPHX_THROW
(
prefix
()
+
"Wrong number of elements"
);
return
*
this
;
}
template
<
class
F
>
bool
same
(
F
f
)
const
{
...
...
src/include/migraphx/concat_opt.hpp
View file @
eb0d8fee
...
...
@@ -9,7 +9,7 @@
#include <utility>
#include <migraphx/operation.hpp>
#include <migraphx/op
erators
.hpp>
#include <migraphx/op
/concat
.hpp>
#include <migraphx/config.hpp>
namespace
migraphx
{
...
...
src/include/migraphx/eliminate_identity.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_IDENTITY_HPP
#define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_IDENTITY_HPP
#include <string>
#include <migraphx/instruction_ref.hpp>
#include <migraphx/config.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
program
;
/**
* Remove identity instructions. Currently when used as the last pass, it will
* preserve the semantics of previous program state, therefore dead code elimination
* should not be used afterwards.
*/
struct
eliminate_identity
{
std
::
string
name
()
const
{
return
"eliminate_identity"
;
}
void
apply
(
program
&
p
)
const
;
};
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/eliminate_pad.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_RTGLIB_ELIMINATE_PAD_HPP
#define MIGRAPHX_GUARD_RTGLIB_ELIMINATE_PAD_HPP
#include <string>
#include <vector>
#include <array>
#include <migraphx/instruction_ref.hpp>
#include <migraphx/config.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
program
;
/**
* Remove pads if they can be written as an
* attribute to another op (im2col, convolution, pooling)
*/
struct
eliminate_pad
{
std
::
string
name
()
const
{
return
"eliminate_pad"
;
}
void
apply
(
program
&
p
)
const
;
template
<
class
T
>
void
update_op
(
T
,
const
instruction_ref
&
input
,
const
instruction_ref
&
ins
,
program
&
p
)
const
;
};
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/env.hpp
View file @
eb0d8fee
...
...
@@ -19,6 +19,8 @@ bool enabled(const char* name);
bool
disabled
(
const
char
*
name
);
std
::
vector
<
std
::
string
>
env
(
const
char
*
name
);
std
::
size_t
value_of
(
const
char
*
name
);
template
<
class
T
>
bool
enabled
(
T
)
{
...
...
@@ -33,6 +35,13 @@ bool disabled(T)
return
result
;
}
template
<
class
T
>
std
::
size_t
value_of
(
T
)
{
static
const
std
::
size_t
result
=
value_of
(
T
::
value
());
return
result
;
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
...
...
src/include/migraphx/functional.hpp
View file @
eb0d8fee
...
...
@@ -137,6 +137,18 @@ auto fold(F f)
return
[
=
](
auto
&&
...
xs
)
{
return
fold_impl
(
f
,
std
::
forward
<
decltype
(
xs
)
>
(
xs
)...);
};
}
template
<
class
F
,
class
Proj
>
auto
by
(
F
f
,
Proj
proj
)
{
return
[
=
](
auto
&&
...
xs
)
{
return
f
(
proj
(
std
::
forward
<
decltype
(
xs
)
>
(
xs
))...);
};
}
template
<
class
T
>
auto
index_of
(
T
&
x
)
{
return
[
&
](
auto
&&
y
)
{
return
x
[
y
];
};
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
...
...
src/include/migraphx/generate.hpp
View file @
eb0d8fee
...
...
@@ -17,7 +17,7 @@ constexpr T normalize(unsigned long z)
return
T
(
0
);
const
auto
max
=
32
;
const
double
range
=
max
/
2
;
// NOLINT
double
result
=
(
z
%
max
)
/
range
;
double
result
=
double
(
z
%
max
)
/
range
;
result
-=
1
;
return
T
(
result
);
}
...
...
src/include/migraphx/instruction.hpp
View file @
eb0d8fee
...
...
@@ -24,7 +24,7 @@ struct instruction
instruction
(
literal
l
);
void
replace
(
const
shape
&
r
);
void
replace
(
operation
o
);
void
recompute_shape
();
...
...
@@ -72,7 +72,9 @@ struct instruction
static
void
replace
(
instruction_ref
ins
,
operation
o
,
const
shape
&
r
,
std
::
vector
<
instruction_ref
>
args
);
argument
eval
()
const
;
bool
can_eval
()
const
;
argument
eval
(
bool
check_eval
=
true
)
const
;
void
finalize
(
context
&
ctx
);
...
...
@@ -88,7 +90,8 @@ struct instruction
// internal
void
replace_argument
(
instruction_ref
old
,
instruction_ref
new_ins
);
private:
void
replace
(
const
shape
&
r
);
operation
op
;
shape
result
;
std
::
vector
<
instruction_ref
>
output
;
...
...
src/include/migraphx/int_divide.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_RTGLIB_INT_DIVIDE_HPP
#define MIGRAPHX_GUARD_RTGLIB_INT_DIVIDE_HPP
#include <migraphx/config.hpp>
#include <cmath>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
template
<
class
R
,
class
T
,
class
U
>
R
floor_divide
(
T
x
,
U
y
)
{
return
R
(
std
::
floor
(
double
(
x
)
/
double
(
y
)));
}
template
<
class
R
,
class
T
,
class
U
>
R
ceil_divide
(
T
x
,
U
y
)
{
return
R
(
std
::
ceil
(
double
(
x
)
/
double
(
y
)));
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/iterator_for.hpp
View file @
eb0d8fee
...
...
@@ -3,21 +3,64 @@
#include <cassert>
#include <type_traits>
#include <iterator>
#include <migraphx/config.hpp>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
template
<
class
T
>
struct
iterator_for_select
{
template
<
class
T
>
static
T
deref
(
T
x
)
{
return
x
;
}
template
<
class
T
>
static
auto
begin
(
T
*
x
)
{
return
x
->
begin
();
}
template
<
class
T
>
static
auto
end
(
T
*
x
)
{
return
x
->
end
();
}
};
struct
iterator_for_select_reverse
{
template
<
class
T
>
static
auto
deref
(
T
x
)
{
return
std
::
prev
(
x
.
base
());
}
template
<
class
T
>
static
auto
begin
(
T
*
x
)
{
return
std
::
make_reverse_iterator
(
x
->
end
());
}
template
<
class
T
>
static
auto
end
(
T
*
x
)
{
return
std
::
make_reverse_iterator
(
x
->
begin
());
}
};
template
<
class
T
,
class
Selector
=
iterator_for_select
>
struct
iterator_for_range
{
T
*
base
;
using
base_iterator
=
std
::
remove_reference_t
<
decltype
(
base
->
begin
())
>
;
using
base_iterator
=
std
::
remove_reference_t
<
decltype
(
Selector
::
begin
(
base
))
>
;
struct
iterator
{
base_iterator
i
;
base_iter
ato
r
operator
*
()
const
{
return
i
;
}
a
u
to
operator
*
()
const
{
return
Selector
::
deref
(
i
)
;
}
base_iterator
operator
++
()
{
return
++
i
;
}
bool
operator
!=
(
const
iterator
&
rhs
)
const
{
return
i
!=
rhs
.
i
;
}
};
...
...
@@ -25,12 +68,12 @@ struct iterator_for_range
iterator
begin
()
{
assert
(
base
!=
nullptr
);
return
{
base
->
begin
()};
return
{
Selector
::
begin
(
base
)};
}
iterator
end
()
{
assert
(
base
!=
nullptr
);
return
{
base
->
end
(
)};
return
{
Selector
::
end
(
base
)};
}
};
template
<
class
T
>
...
...
@@ -39,6 +82,12 @@ iterator_for_range<T> iterator_for(T& x)
return
{
&
x
};
}
template
<
class
T
>
iterator_for_range
<
T
,
iterator_for_select_reverse
>
reverse_iterator_for
(
T
&
x
)
{
return
{
&
x
};
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
...
...
src/include/migraphx/literal.hpp
View file @
eb0d8fee
...
...
@@ -22,8 +22,8 @@ struct literal : raw_data<literal>
{
literal
()
{}
template
<
class
U
,
class
T
=
deduce
<
U
>
>
literal
(
U
x
)
:
buffer
(
make_shared_array
<
char
>
(
sizeof
(
T
))),
m_shape
(
s
hape
::
get_type
<
T
>
{}
)
template
<
class
U
,
class
T
=
deduce
<
U
>
,
shape
::
type_t
ShapeType
=
shape
::
get_type
<
T
>
{}
>
literal
(
U
x
)
:
buffer
(
make_shared_array
<
char
>
(
sizeof
(
T
))),
m_shape
(
S
hape
Type
)
{
static_assert
(
std
::
is_trivially_copyable
<
T
>
{},
"Literals can only be trivial types"
);
*
(
reinterpret_cast
<
T
*>
(
buffer
.
get
()))
=
x
;
...
...
src/include/migraphx/make_signed.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_RTGLIB_MAKE_SIGNED_HPP
#define MIGRAPHX_GUARD_RTGLIB_MAKE_SIGNED_HPP
#include <migraphx/config.hpp>
#include <type_traits>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
template
<
class
T
>
typename
std
::
conditional_t
<
std
::
is_integral
<
T
>
{},
std
::
make_signed
<
T
>
,
std
::
enable_if
<
true
,
T
>>::
type
make_signed
(
T
x
)
{
return
x
;
}
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/onnx.hpp
View file @
eb0d8fee
...
...
@@ -7,24 +7,6 @@
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
struct
unknown
{
std
::
string
op
;
std
::
string
name
()
const
{
return
"unknown:"
+
op
;
}
shape
compute_shape
(
std
::
vector
<
shape
>
input
)
const
{
if
(
input
.
empty
())
return
{};
else
return
input
.
front
();
}
friend
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
unknown
&
x
)
{
os
<<
x
.
name
();
return
os
;
}
};
/// Create a program from an onnx file
program
parse_onnx
(
const
std
::
string
&
name
);
...
...
src/include/migraphx/op/abnormal_ops.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ABNORMAL_OPS_HPP
#define MIGRAPHX_GUARD_OPERATORS_ABNORMAL_OPS_HPP
#include <array>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
not_computable
{
argument
compute
(
const
shape
&
,
const
std
::
vector
<
argument
>&
)
const
{
MIGRAPHX_THROW
(
"not computable"
);
}
};
struct
undefined
{
std
::
string
name
()
const
{
return
"undefined"
;
}
shape
compute_shape
(
const
std
::
vector
<
shape
>&
inputs
)
const
{
check_shapes
{
inputs
,
*
this
}.
has
(
0
);
return
{};
}
argument
compute
(
const
shape
&
,
const
std
::
vector
<
argument
>&
)
const
{
return
{{},
nullptr
};
}
};
struct
unknown
{
std
::
string
op
;
template
<
class
Self
,
class
F
>
static
auto
reflect
(
Self
&
self
,
F
f
)
{
return
pack
(
f
(
self
.
op
,
"op"
));
}
std
::
string
name
()
const
{
return
"unknown:"
+
op
;
}
shape
compute_shape
(
std
::
vector
<
shape
>
input
)
const
{
if
(
input
.
empty
())
return
{};
else
return
input
.
front
();
}
friend
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
unknown
&
x
)
{
os
<<
x
.
name
();
return
os
;
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/abs.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ABS_HPP
#define MIGRAPHX_GUARD_OPERATORS_ABS_HPP
#include <array>
#include <migraphx/op/unary.hpp>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <migraphx/make_signed.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
abs
:
unary
<
abs
>
{
auto
apply
()
const
{
return
[](
auto
x
)
{
return
std
::
abs
(
make_signed
(
x
));
};
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/acos.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ACOS_HPP
#define MIGRAPHX_GUARD_OPERATORS_ACOS_HPP
#include <array>
#include <migraphx/op/unary.hpp>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
acos
:
unary
<
acos
>
{
auto
apply
()
const
{
return
[](
auto
x
)
{
return
std
::
acos
(
x
);
};
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/add.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ADD_HPP
#define MIGRAPHX_GUARD_OPERATORS_ADD_HPP
#include <array>
#include <migraphx/op/binary.hpp>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
add
:
binary
<
add
>
{
auto
apply
()
const
{
return
[](
auto
x
,
auto
y
)
{
return
x
+
y
;
};
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/as_shape.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_AS_SHAPE_HPP
#define MIGRAPHX_GUARD_OPERATORS_AS_SHAPE_HPP
#include <array>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
as_shape
{
shape
s
;
template
<
class
Self
,
class
F
>
static
auto
reflect
(
Self
&
self
,
F
f
)
{
return
pack
(
f
(
self
.
s
,
"shape"
));
}
std
::
string
name
()
const
{
return
"as_shape"
;
}
shape
compute_shape
(
const
std
::
vector
<
shape
>&
inputs
)
const
{
check_shapes
{
inputs
,
*
this
}.
has
(
1
).
standard
();
assert
(
inputs
.
front
().
elements
()
==
s
.
elements
());
return
s
;
}
argument
compute
(
shape
output_shape
,
std
::
vector
<
argument
>
args
)
const
{
return
{
std
::
move
(
output_shape
),
std
::
move
(
args
.
front
().
data
)};
}
std
::
ptrdiff_t
output_alias
(
const
std
::
vector
<
shape
>&
)
const
{
return
0
;
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/asin.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ASIN_HPP
#define MIGRAPHX_GUARD_OPERATORS_ASIN_HPP
#include <array>
#include <migraphx/op/unary.hpp>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
asin
:
unary
<
asin
>
{
auto
apply
()
const
{
return
[](
auto
x
)
{
return
std
::
asin
(
x
);
};
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
src/include/migraphx/op/atan.hpp
0 → 100644
View file @
eb0d8fee
#ifndef MIGRAPHX_GUARD_OPERATORS_ATAN_HPP
#define MIGRAPHX_GUARD_OPERATORS_ATAN_HPP
#include <array>
#include <migraphx/op/unary.hpp>
#include <migraphx/operation.hpp>
#include <migraphx/check_shapes.hpp>
#include <migraphx/stringutils.hpp>
#include <migraphx/streamutils.hpp>
#include <migraphx/literal.hpp>
#include <migraphx/shape_for_each.hpp>
#include <migraphx/config.hpp>
#include <cmath>
#include <utility>
namespace
migraphx
{
inline
namespace
MIGRAPHX_INLINE_NS
{
namespace
op
{
struct
atan
:
unary
<
atan
>
{
auto
apply
()
const
{
return
[](
auto
x
)
{
return
std
::
atan
(
x
);
};
}
};
}
// namespace op
}
// namespace MIGRAPHX_INLINE_NS
}
// namespace migraphx
#endif
Prev
1
2
3
4
5
6
…
16
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment