Commit 022dcc46 by Bryce McKinlay

expr.c (java_array_data_offset): Removed function.

	* expr.c (java_array_data_offset): Removed function.
	(JAVA_ARRAY_LENGTH_OFFSET): Removed macro.
	(build_java_array_length_access): Obtain "length" value using a
	COMPONENT_REF, instead of INDIRECT_REF and arithmetic.
	(build_java_arrayaccess): Correct comment. Access "data" using a
	COMPONENT_REF, and return an ARRAY_REF instead of an INDIRECT_REF.
	(build_java_arraystore_check): New function.
	(expand_java_arraystore): Use build_java_arraystore_check.
	* parse.y (patch_assignment): Simplify code to insert a store check
	when lvalue is an ARRAY_REF. Use build_java_arraystore_check.
	* check-init.c (check_init): Update to reflect that an array length
	access is now a COMPONENT_REF.
	* gcj.texi (Code Generation): Improve documentation of
	-fno-bounds-check. Add documentation for -fno-store-check.
	* java-tree.h (flag_store_check): Declare.
	(build_java_arraystore_check): Declare.
	* lang.c (flag_store_check): Initialize to 1.
	(lang_f_options): Add store-check option.
	* jvspec.c: Don't pass store-check option to jvgenmain.
	* lang-options.h: Add help string for -fno-store-check.

From-SVN: r50129
parent 70da1d03
2002-02-28 Bryce McKinlay <bryce@waitaki.otago.ac.nz>
* expr.c (java_array_data_offset): Removed function.
(JAVA_ARRAY_LENGTH_OFFSET): Removed macro.
(build_java_array_length_access): Obtain "length" value using a
COMPONENT_REF, instead of INDIRECT_REF and arithmetic.
(build_java_arrayaccess): Correct comment. Access "data" using a
COMPONENT_REF, and return an ARRAY_REF instead of an INDIRECT_REF.
(build_java_arraystore_check): New function.
(expand_java_arraystore): Use build_java_arraystore_check.
* parse.y (patch_assignment): Simplify code to insert a store check
when lvalue is an ARRAY_REF. Use build_java_arraystore_check.
* check-init.c (check_init): Update to reflect that an array length
access is now a COMPONENT_REF.
* gcj.texi (Code Generation): Improve documentation of
-fno-bounds-check. Add documentation for -fno-store-check.
* java-tree.h (flag_store_check): Declare.
(build_java_arraystore_check): Declare.
* lang.c (flag_store_check): Initialize to 1.
(lang_f_options): Add store-check option.
* jvspec.c: Don't pass store-check option to jvgenmain.
* lang-options.h: Add help string for -fno-store-check.
2002-02-28 Neil Booth <neil@daikokuya.demon.co.uk> 2002-02-28 Neil Booth <neil@daikokuya.demon.co.uk>
* decl.c (copy_lang_decl): Rename java_dup_lang_specific_decl. * decl.c (copy_lang_decl): Rename java_dup_lang_specific_decl.
...@@ -20,7 +43,7 @@ ...@@ -20,7 +43,7 @@
2002-02-22 Per Bothner <per@bothner.com> 2002-02-22 Per Bothner <per@bothner.com>
* class.c: Change vtable to be more compatible with g++ v3 abi. * class.c: Change vtable to be more compatible with g++ v3 abi.
(get_dispatch_table): Prepend offset-to-top (always 0) and (get_dispatch_table): Prepend offset-to-top (always 0) and
type_info pointer (currently unimplemented hence NULL) to vtable. type_info pointer (currently unimplemented hence NULL) to vtable.
Specifically, prepend offset-to-top and typeinfo ptr (currently null). Specifically, prepend offset-to-top and typeinfo ptr (currently null).
......
...@@ -558,7 +558,7 @@ check_init (exp, before) ...@@ -558,7 +558,7 @@ check_init (exp, before)
final_assign_error (DECL_NAME (decl)); final_assign_error (DECL_NAME (decl));
break; break;
} }
else if (TREE_CODE (tmp) == INDIRECT_REF && IS_ARRAY_LENGTH_ACCESS (tmp)) else if (TREE_CODE (tmp) == COMPONENT_REF && IS_ARRAY_LENGTH_ACCESS (tmp))
{ {
/* We can't emit a more specific message here, because when /* We can't emit a more specific message here, because when
compiling to bytecodes we don't get here. */ compiling to bytecodes we don't get here. */
......
...@@ -79,7 +79,6 @@ static void java_push_constant_from_pool PARAMS ((struct JCF *, int)); ...@@ -79,7 +79,6 @@ static void java_push_constant_from_pool PARAMS ((struct JCF *, int));
static void java_stack_pop PARAMS ((int)); static void java_stack_pop PARAMS ((int));
static tree build_java_throw_out_of_bounds_exception PARAMS ((tree)); static tree build_java_throw_out_of_bounds_exception PARAMS ((tree));
static tree build_java_check_indexed_type PARAMS ((tree, tree)); static tree build_java_check_indexed_type PARAMS ((tree, tree));
static tree java_array_data_offset PARAMS ((tree));
static tree case_identity PARAMS ((tree, tree)); static tree case_identity PARAMS ((tree, tree));
static unsigned char peek_opcode_at_pc PARAMS ((struct JCF *, int, int)); static unsigned char peek_opcode_at_pc PARAMS ((struct JCF *, int, int));
static bool emit_init_test_initialization PARAMS ((struct hash_entry *, static bool emit_init_test_initialization PARAMS ((struct hash_entry *,
...@@ -628,11 +627,6 @@ build_java_ret (location) ...@@ -628,11 +627,6 @@ build_java_ret (location)
/* Implementation of operations on array: new, load, store, length */ /* Implementation of operations on array: new, load, store, length */
/* Array core info access macros */
#define JAVA_ARRAY_LENGTH_OFFSET(A) \
byte_position (TREE_CHAIN (TYPE_FIELDS (TREE_TYPE (TREE_TYPE (A)))))
tree tree
decode_newarray_type (atype) decode_newarray_type (atype)
int atype; int atype;
...@@ -699,6 +693,7 @@ build_java_array_length_access (node) ...@@ -699,6 +693,7 @@ build_java_array_length_access (node)
tree node; tree node;
{ {
tree type = TREE_TYPE (node); tree type = TREE_TYPE (node);
tree array_type = TREE_TYPE (type);
HOST_WIDE_INT length; HOST_WIDE_INT length;
if (!is_array_type_p (type)) if (!is_array_type_p (type))
...@@ -707,13 +702,13 @@ build_java_array_length_access (node) ...@@ -707,13 +702,13 @@ build_java_array_length_access (node)
length = java_array_type_length (type); length = java_array_type_length (type);
if (length >= 0) if (length >= 0)
return build_int_2 (length, 0); return build_int_2 (length, 0);
node = build1 (INDIRECT_REF, int_type_node,
fold (build (PLUS_EXPR, ptr_type_node, node = build (COMPONENT_REF, int_type_node,
java_check_reference (node, build_java_indirect_ref (array_type, node,
flag_check_references), flag_check_references),
JAVA_ARRAY_LENGTH_OFFSET(node)))); lookup_field (&array_type, get_identifier ("length")));
IS_ARRAY_LENGTH_ACCESS (node) = 1; IS_ARRAY_LENGTH_ACCESS (node) = 1;
return fold (node); return node;
} }
/* Optionally checks a reference against the NULL pointer. ARG1: the /* Optionally checks a reference against the NULL pointer. ARG1: the
...@@ -752,19 +747,6 @@ build_java_indirect_ref (type, expr, check) ...@@ -752,19 +747,6 @@ build_java_indirect_ref (type, expr, check)
return build1 (INDIRECT_REF, type, java_check_reference (expr, check)); return build1 (INDIRECT_REF, type, java_check_reference (expr, check));
} }
static tree
java_array_data_offset (array)
tree array;
{
tree array_type = TREE_TYPE (TREE_TYPE (array));
tree data_fld = TREE_CHAIN (TREE_CHAIN (TYPE_FIELDS (array_type)));
if (data_fld == NULL_TREE)
return size_in_bytes (array_type);
else
return byte_position (data_fld);
}
/* Implement array indexing (either as l-value or r-value). /* Implement array indexing (either as l-value or r-value).
Returns a tree for ARRAY[INDEX], assume TYPE is the element type. Returns a tree for ARRAY[INDEX], assume TYPE is the element type.
Optionally performs bounds checking and/or test to NULL. Optionally performs bounds checking and/or test to NULL.
...@@ -774,12 +756,10 @@ tree ...@@ -774,12 +756,10 @@ tree
build_java_arrayaccess (array, type, index) build_java_arrayaccess (array, type, index)
tree array, type, index; tree array, type, index;
{ {
tree arith, node, throw = NULL_TREE; tree node, throw = NULL_TREE;
tree data_field;
arith = fold (build (PLUS_EXPR, int_type_node, tree ref;
java_array_data_offset (array), tree array_type = TREE_TYPE (TREE_TYPE (array));
fold (build (MULT_EXPR, int_type_node,
index, size_in_bytes(type)))));
if (flag_bounds_check) if (flag_bounds_check)
{ {
...@@ -803,23 +783,86 @@ build_java_arrayaccess (array, type, index) ...@@ -803,23 +783,86 @@ build_java_arrayaccess (array, type, index)
} }
} }
/* The SAVE_EXPR is for correct evaluation order. It would be /* If checking bounds, wrap the index expr with a COMPOUND_EXPR in order
cleaner to use force_evaluation_order (see comment there), but to have the bounds check evaluated first. */
that is difficult when we also have to deal with bounds if (throw != NULL_TREE)
checking. The SAVE_EXPR is not necessary to do that when we're index = build (COMPOUND_EXPR, int_type_node, throw, index);
not checking for array bounds. */
if (TREE_SIDE_EFFECTS (index) && throw) data_field = lookup_field (&array_type, get_identifier ("data"));
throw = build (COMPOUND_EXPR, int_type_node, save_expr (array), throw);
ref = build (COMPONENT_REF, TREE_TYPE (data_field),
node = build1 (INDIRECT_REF, type, build_java_indirect_ref (array_type, array,
fold (build (PLUS_EXPR, ptr_type_node, flag_check_references),
java_check_reference (array, data_field);
flag_check_references),
(throw ? build (COMPOUND_EXPR, int_type_node, node = build (ARRAY_REF, type, ref, index);
throw, arith ) : arith))));
return node; return node;
} }
/* Generate code to throw an ArrayStoreException if OBJECT is not assignable
(at runtime) to an element of ARRAY. A NOP_EXPR is returned if it can
determine that no check is required. */
tree
build_java_arraystore_check (array, object)
tree array;
tree object;
{
tree check, element_type;
tree array_type_p = TREE_TYPE (array);
tree object_type = TYPE_NAME (TREE_TYPE (TREE_TYPE (object)));
if (! is_array_type_p (array_type_p))
abort ();
/* Get the TYPE_DECL for ARRAY's element type. */
element_type = TYPE_NAME (TREE_TYPE (TREE_TYPE (TREE_TYPE (array_type_p))));
if (TREE_CODE (element_type) != TYPE_DECL
|| TREE_CODE (object_type) != TYPE_DECL)
abort ();
if (!flag_store_check)
return build1 (NOP_EXPR, array_type_p, array);
/* No check is needed if the element type is final or is itself an array.
Also check that element_type matches object_type, since in the bytecode
compilation case element_type may be the actual element type of the arra
rather than its declared type. */
if (element_type == object_type
&& (TYPE_ARRAY_P (TREE_TYPE (element_type))
|| CLASS_FINAL (element_type)))
return build1 (NOP_EXPR, array_type_p, array);
/* Avoid the check if OBJECT was just loaded from the same array. */
if (TREE_CODE (object) == ARRAY_REF)
{
tree target;
tree source = TREE_OPERAND (object, 0); /* COMPONENT_REF. */
source = TREE_OPERAND (source, 0); /* INDIRECT_REF. */
source = TREE_OPERAND (source, 0); /* Source array's DECL or SAVE_EXPR. */
if (TREE_CODE (source) == SAVE_EXPR)
source = TREE_OPERAND (source, 0);
target = array;
if (TREE_CODE (target) == SAVE_EXPR)
target = TREE_OPERAND (target, 0);
if (source == target)
return build1 (NOP_EXPR, array_type_p, array);
}
/* Build an invocation of _Jv_CheckArrayStore */
check = build (CALL_EXPR, void_type_node,
build_address_of (soft_checkarraystore_node),
tree_cons (NULL_TREE, array,
build_tree_list (NULL_TREE, object)),
NULL_TREE);
TREE_SIDE_EFFECTS (check) = 1;
return check;
}
/* Makes sure that INDEXED_TYPE is appropriate. If not, make it from /* Makes sure that INDEXED_TYPE is appropriate. If not, make it from
ARRAY_NODE. This function is used to retrieve something less vague than ARRAY_NODE. This function is used to retrieve something less vague than
a pointer type when indexing the first dimension of something like [[<t>. a pointer type when indexing the first dimension of something like [[<t>.
...@@ -973,12 +1016,7 @@ expand_java_arraystore (rhs_type_node) ...@@ -973,12 +1016,7 @@ expand_java_arraystore (rhs_type_node)
if (TREE_CODE (rhs_type_node) == POINTER_TYPE) if (TREE_CODE (rhs_type_node) == POINTER_TYPE)
{ {
tree check = build (CALL_EXPR, void_type_node, tree check = build_java_arraystore_check (array, rhs_node);
build_address_of (soft_checkarraystore_node),
tree_cons (NULL_TREE, array,
build_tree_list (NULL_TREE, rhs_node)),
NULL_TREE);
TREE_SIDE_EFFECTS (check) = 1;
expand_expr_stmt (check); expand_expr_stmt (check);
} }
......
...@@ -392,9 +392,20 @@ directory. ...@@ -392,9 +392,20 @@ directory.
@item -fno-bounds-check @item -fno-bounds-check
By default, @code{gcj} generates code which checks the bounds of all By default, @code{gcj} generates code which checks the bounds of all
array indexing operations. With this option, these checks are omitted. array indexing operations. With this option, these checks are omitted, which
Note that this can result in unpredictable behavior if the code in can improve performance for code that uses arrays extensively. Note that this
question actually does violate array bounds constraints. can result in unpredictable behavior if the code in question actually does
violate array bounds constraints. It is safe to use this option if you are
sure that your code will never throw an @code{ArrayIndexOutOfBoundsException}.
@item -fno-store-check
Don't generate array store checks. When storing objects into arrays, a runtime
check is normally generated in order to ensure that the object is assignment
compatible with the component type of the array (which may not be known
at compile-time). With this option, these checks are omitted. This can
improve performance for code which stores objects into arrays frequently.
It is safe to use this option if you are sure your code will never throw an
@code{ArrayStoreException}.
@item -fjni @item -fjni
With @code{gcj} there are two options for writing native methods: CNI With @code{gcj} there are two options for writing native methods: CNI
......
...@@ -213,6 +213,9 @@ extern int flag_optimize_sci; ...@@ -213,6 +213,9 @@ extern int flag_optimize_sci;
in order to improve binary compatibility. */ in order to improve binary compatibility. */
extern int flag_indirect_dispatch; extern int flag_indirect_dispatch;
/* When zero, don't generate runtime array store checks. */
extern int flag_store_check;
/* Encoding used for source files. */ /* Encoding used for source files. */
extern const char *current_encoding; extern const char *current_encoding;
...@@ -1107,6 +1110,7 @@ extern tree build_java_binop PARAMS ((enum tree_code, tree, tree, tree)); ...@@ -1107,6 +1110,7 @@ extern tree build_java_binop PARAMS ((enum tree_code, tree, tree, tree));
extern tree build_java_soft_divmod PARAMS ((enum tree_code, tree, tree, tree)); extern tree build_java_soft_divmod PARAMS ((enum tree_code, tree, tree, tree));
extern tree binary_numeric_promotion PARAMS ((tree, tree, tree *, tree *)); extern tree binary_numeric_promotion PARAMS ((tree, tree, tree *, tree *));
extern tree build_java_arrayaccess PARAMS ((tree, tree, tree)); extern tree build_java_arrayaccess PARAMS ((tree, tree, tree));
extern tree build_java_arraystore_check PARAMS ((tree, tree));
extern tree build_newarray PARAMS ((int, tree)); extern tree build_newarray PARAMS ((int, tree));
extern tree build_anewarray PARAMS ((tree, tree)); extern tree build_anewarray PARAMS ((tree, tree));
extern tree build_new_array PARAMS ((tree, tree)); extern tree build_new_array PARAMS ((tree, tree));
......
...@@ -66,6 +66,7 @@ static const char jvgenmain_spec[] = ...@@ -66,6 +66,7 @@ static const char jvgenmain_spec[] =
%{<femit-class-file} %{<femit-class-files} %{<fencoding*}\ %{<femit-class-file} %{<femit-class-files} %{<fencoding*}\
%{<fuse-boehm-gc} %{<fhash-synchronization} %{<fjni}\ %{<fuse-boehm-gc} %{<fhash-synchronization} %{<fjni}\
%{<findirect-dispatch} \ %{<findirect-dispatch} \
%{<fno-store-check}\
%{<fclasspath*} %{<fCLASSPATH*} %{<foutput-class-dir}\ %{<fclasspath*} %{<fCLASSPATH*} %{<foutput-class-dir}\
%{<fuse-divide-subroutine} %{<fno-use-divide-subroutine}\ %{<fuse-divide-subroutine} %{<fno-use-divide-subroutine}\
%{<fcheck-references} %{<fno-check-references}\ %{<fcheck-references} %{<fno-check-references}\
......
...@@ -30,6 +30,8 @@ DEFINE_LANG_NAME ("Java") ...@@ -30,6 +30,8 @@ DEFINE_LANG_NAME ("Java")
{ "-fbounds-check", "" }, { "-fbounds-check", "" },
{ "-fno-bounds-check", { "-fno-bounds-check",
N_("Disable automatic array bounds checking") }, N_("Disable automatic array bounds checking") },
{ "-fno-store-check",
N_("Disable assignability checks for stores into object arrays") },
{ "-fjni", { "-fjni",
N_("Assume native functions are implemented using JNI") }, N_("Assume native functions are implemented using JNI") },
{ "--CLASSPATH", { "--CLASSPATH",
...@@ -54,3 +56,5 @@ DEFINE_LANG_NAME ("Java") ...@@ -54,3 +56,5 @@ DEFINE_LANG_NAME ("Java")
N_("Always check for non gcj generated classes archives") }, N_("Always check for non gcj generated classes archives") },
{ "-fno-optimize-static-class-initialization", { "-fno-optimize-static-class-initialization",
N_("Never optimize static class initialization code") }, N_("Never optimize static class initialization code") },
{ "-findirect-dispatch",
N_("Use offset tables for virtual method calls") },
...@@ -157,6 +157,9 @@ int flag_optimize_sci = 1; ...@@ -157,6 +157,9 @@ int flag_optimize_sci = 1;
in order to improve binary compatibility. */ in order to improve binary compatibility. */
int flag_indirect_dispatch = 0; int flag_indirect_dispatch = 0;
/* When zero, don't generate runtime array store checks. */
int flag_store_check = 1;
/* When non zero, print extra version information. */ /* When non zero, print extra version information. */
static int version_flag = 0; static int version_flag = 0;
...@@ -179,7 +182,8 @@ lang_f_options[] = ...@@ -179,7 +182,8 @@ lang_f_options[] =
{"check-references", &flag_check_references, 1}, {"check-references", &flag_check_references, 1},
{"force-classes-archive-check", &flag_force_classes_archive_check, 1}, {"force-classes-archive-check", &flag_force_classes_archive_check, 1},
{"optimize-static-class-initialization", &flag_optimize_sci, 1 }, {"optimize-static-class-initialization", &flag_optimize_sci, 1 },
{"indirect-dispatch", &flag_indirect_dispatch, 1} {"indirect-dispatch", &flag_indirect_dispatch, 1},
{"store-check", &flag_store_check, 1}
}; };
static const struct string_option static const struct string_option
......
...@@ -12585,9 +12585,8 @@ patch_assignment (node, wfl_op1) ...@@ -12585,9 +12585,8 @@ patch_assignment (node, wfl_op1)
{ {
lhs_type = TREE_TYPE (lvalue); lhs_type = TREE_TYPE (lvalue);
} }
/* Or Lhs can be a array access. Should that be lvalue ? FIXME + /* Or Lhs can be an array access. */
comment on reason why */ else if (TREE_CODE (lvalue) == ARRAY_REF)
else if (TREE_CODE (wfl_op1) == ARRAY_REF)
{ {
lhs_type = TREE_TYPE (lvalue); lhs_type = TREE_TYPE (lvalue);
lvalue_from_array = 1; lvalue_from_array = 1;
...@@ -12689,80 +12688,29 @@ patch_assignment (node, wfl_op1) ...@@ -12689,80 +12688,29 @@ patch_assignment (node, wfl_op1)
&& lvalue_from_array && lvalue_from_array
&& JREFERENCE_TYPE_P (TYPE_ARRAY_ELEMENT (lhs_type))) && JREFERENCE_TYPE_P (TYPE_ARRAY_ELEMENT (lhs_type)))
{ {
tree check; tree array, store_check, base, index_expr;
tree base = lvalue;
/* We need to retrieve the right argument for /* Get the INDIRECT_REF. */
_Jv_CheckArrayStore. This is somewhat complicated by bounds array = TREE_OPERAND (TREE_OPERAND (lvalue, 0), 0);
and null pointer checks, both of which wrap the operand in /* Get the array pointer expr. */
one layer of COMPOUND_EXPR. */ array = TREE_OPERAND (array, 0);
if (TREE_CODE (lvalue) == COMPOUND_EXPR) store_check = build_java_arraystore_check (array, new_rhs);
base = TREE_OPERAND (lvalue, 0);
else index_expr = TREE_OPERAND (lvalue, 1);
if (TREE_CODE (index_expr) == COMPOUND_EXPR)
{ {
tree op = TREE_OPERAND (base, 0); /* A COMPOUND_EXPR here is a bounds check. The bounds check must
happen before the store check, so prepare to insert the store
/* We can have a SAVE_EXPR here when doing String +=. */ check within the second operand of the existing COMPOUND_EXPR. */
if (TREE_CODE (op) == SAVE_EXPR) base = index_expr;
op = TREE_OPERAND (op, 0);
/* We can have a COMPOUND_EXPR here when doing bounds check. */
if (TREE_CODE (op) == COMPOUND_EXPR)
op = TREE_OPERAND (op, 1);
base = TREE_OPERAND (op, 0);
/* Strip the last PLUS_EXPR to obtain the base. */
if (TREE_CODE (base) == PLUS_EXPR)
base = TREE_OPERAND (base, 0);
}
/* Build the invocation of _Jv_CheckArrayStore */
new_rhs = save_expr (new_rhs);
check = build (CALL_EXPR, void_type_node,
build_address_of (soft_checkarraystore_node),
tree_cons (NULL_TREE, base,
build_tree_list (NULL_TREE, new_rhs)),
NULL_TREE);
TREE_SIDE_EFFECTS (check) = 1;
/* We have to decide on an insertion point */
if (TREE_CODE (lvalue) == COMPOUND_EXPR)
{
tree t;
if (flag_bounds_check)
{
t = TREE_OPERAND (TREE_OPERAND (TREE_OPERAND (lvalue, 1), 0), 0);
TREE_OPERAND (TREE_OPERAND (TREE_OPERAND (lvalue, 1), 0), 0) =
build (COMPOUND_EXPR, void_type_node, t, check);
}
else
TREE_OPERAND (lvalue, 1) = build (COMPOUND_EXPR, lhs_type,
check, TREE_OPERAND (lvalue, 1));
} }
else if (flag_bounds_check)
{
tree hook = lvalue;
tree compound = TREE_OPERAND (lvalue, 0);
tree bound_check, new_compound;
if (TREE_CODE (compound) == SAVE_EXPR)
{
compound = TREE_OPERAND (compound, 0);
hook = TREE_OPERAND (hook, 0);
}
/* Find the array bound check, hook the original array access. */
bound_check = TREE_OPERAND (compound, 0);
TREE_OPERAND (hook, 0) = TREE_OPERAND (compound, 1);
/* Make sure the bound check will happen before the store check */
new_compound =
build (COMPOUND_EXPR, void_type_node, bound_check, check);
/* Re-assemble the augmented array access. */
lvalue = build (COMPOUND_EXPR, TREE_TYPE (lvalue),
new_compound, lvalue);
}
else else
lvalue = build (COMPOUND_EXPR, TREE_TYPE (lvalue), check, lvalue); base = lvalue;
index_expr = TREE_OPERAND (base, 1);
TREE_OPERAND (base, 1) = build (COMPOUND_EXPR, TREE_TYPE (index_expr),
store_check, index_expr);
} }
/* Final locals can be used as case values in switch /* Final locals can be used as case values in switch
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment