Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
Pyston
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
Pyston
Commits
51b685f5
Commit
51b685f5
authored
Nov 19, 2015
by
Kevin Modzelewski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Basic refcounting in the interpreter
parent
e3dcb351
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
93 additions
and
37 deletions
+93
-37
src/codegen/ast_interpreter.cpp
src/codegen/ast_interpreter.cpp
+56
-13
src/codegen/irgen/hooks.cpp
src/codegen/irgen/hooks.cpp
+4
-2
src/core/stringpool.cpp
src/core/stringpool.cpp
+2
-1
src/runtime/objmodel.cpp
src/runtime/objmodel.cpp
+7
-3
src/runtime/types.cpp
src/runtime/types.cpp
+14
-11
src/runtime/types.h
src/runtime/types.h
+10
-7
No files found.
src/codegen/ast_interpreter.cpp
View file @
51b685f5
...
...
@@ -53,6 +53,8 @@
namespace
pyston
{
static
int
calculateNumVRegs
(
FunctionMetadata
*
md
);
namespace
{
class
ASTInterpreter
;
...
...
@@ -76,8 +78,8 @@ public:
private:
Value
createFunction
(
AST
*
node
,
AST_arguments
*
args
,
const
std
::
vector
<
AST_stmt
*>&
body
);
Value
doBinOp
(
AST_expr
*
node
,
Value
left
,
Value
right
,
int
op
,
BinExpType
exp_type
);
void
doStore
(
AST_expr
*
node
,
Value
value
);
void
doStore
(
AST_Name
*
name
,
Value
value
);
void
doStore
(
AST_expr
*
node
,
STOLEN
(
Value
)
value
);
void
doStore
(
AST_Name
*
name
,
STOLEN
(
Value
)
value
);
Box
*
doOSR
(
AST_Jump
*
node
);
Value
getNone
();
...
...
@@ -157,6 +159,18 @@ private:
bool
should_jit
;
public:
~
ASTInterpreter
()
{
Py_XDECREF
(
frame_info
.
boxedLocals
);
int
nvregs
=
calculateNumVRegs
(
md
);
for
(
int
i
=
0
;
i
<
nvregs
;
i
++
)
{
Py_XDECREF
(
vregs
[
i
]);
}
Py_DECREF
(
this
->
globals
);
}
llvm
::
DenseMap
<
InternedString
,
int
>&
getSymVRegMap
()
{
assert
(
source_info
->
cfg
);
return
source_info
->
cfg
->
sym_vreg_map
;
...
...
@@ -224,6 +238,7 @@ void ASTInterpreter::setFrameInfo(const FrameInfo* frame_info) {
void
ASTInterpreter
::
setGlobals
(
Box
*
globals
)
{
this
->
globals
=
globals
;
Py_INCREF
(
globals
);
}
ASTInterpreter
::
ASTInterpreter
(
FunctionMetadata
*
md
,
Box
**
vregs
)
...
...
@@ -285,6 +300,8 @@ void ASTInterpreter::startJITing(CFGBlock* block, int exit_offset) {
assert
(
ENABLE_BASELINEJIT
);
assert
(
!
jit
);
assert
(
0
&&
"refcounting not set up"
);
auto
&
code_blocks
=
md
->
code_blocks
;
JitCodeBlock
*
code_block
=
NULL
;
if
(
!
code_blocks
.
empty
())
...
...
@@ -340,7 +357,7 @@ Box* ASTInterpreter::execJITedBlock(CFGBlock* b) {
}
Box
*
ASTInterpreter
::
executeInner
(
ASTInterpreter
&
interpreter
,
CFGBlock
*
start_block
,
AST_stmt
*
start_at
)
{
Value
v
;
Value
v
(
nullptr
,
nullptr
)
;
bool
from_start
=
start_block
==
NULL
&&
start_at
==
NULL
;
...
...
@@ -366,6 +383,7 @@ Box* ASTInterpreter::executeInner(ASTInterpreter& interpreter, CFGBlock* start_b
}
interpreter
.
current_inst
=
s
;
Py_XDECREF
(
v
.
o
);
v
=
interpreter
.
visit_stmt
(
s
);
}
}
else
{
...
...
@@ -398,6 +416,7 @@ Box* ASTInterpreter::executeInner(ASTInterpreter& interpreter, CFGBlock* start_b
interpreter
.
current_inst
=
s
;
if
(
interpreter
.
jit
)
interpreter
.
jit
->
emitSetCurrentInst
(
s
);
Py_XDECREF
(
v
.
o
);
v
=
interpreter
.
visit_stmt
(
s
);
}
}
...
...
@@ -434,17 +453,19 @@ Value ASTInterpreter::doBinOp(AST_expr* node, Value left, Value right, int op, B
return
Value
();
}
void
ASTInterpreter
::
doStore
(
AST_Name
*
node
,
Value
value
)
{
void
ASTInterpreter
::
doStore
(
AST_Name
*
node
,
STOLEN
(
Value
)
value
)
{
if
(
node
->
lookup_type
==
ScopeInfo
::
VarScopeType
::
UNKNOWN
)
node
->
lookup_type
=
scope_info
->
getScopeTypeOfName
(
node
->
id
);
InternedString
name
=
node
->
id
;
ScopeInfo
::
VarScopeType
vst
=
node
->
lookup_type
;
if
(
vst
==
ScopeInfo
::
VarScopeType
::
GLOBAL
)
{
assert
(
0
&&
"check refcounting"
);
if
(
jit
)
jit
->
emitSetGlobal
(
globals
,
name
.
getBox
(),
value
);
setGlobal
(
globals
,
name
.
getBox
(),
value
.
o
);
}
else
if
(
vst
==
ScopeInfo
::
VarScopeType
::
NAME
)
{
assert
(
0
&&
"check refcounting"
);
if
(
jit
)
jit
->
emitSetItemName
(
name
.
getBox
(),
value
);
assert
(
frame_info
.
boxedLocals
!=
NULL
);
...
...
@@ -453,6 +474,7 @@ void ASTInterpreter::doStore(AST_Name* node, Value value) {
}
else
{
bool
closure
=
vst
==
ScopeInfo
::
VarScopeType
::
CLOSURE
;
if
(
jit
)
{
assert
(
0
&&
"check refcounting"
);
if
(
!
closure
)
{
bool
is_live
=
source_info
->
getLiveness
()
->
isLiveAtEnd
(
name
,
current_block
);
if
(
is_live
)
...
...
@@ -465,9 +487,12 @@ void ASTInterpreter::doStore(AST_Name* node, Value value) {
assert
(
getSymVRegMap
().
count
(
name
));
assert
(
getSymVRegMap
()[
name
]
==
node
->
vreg
);
Box
*
prev
=
vregs
[
node
->
vreg
];
vregs
[
node
->
vreg
]
=
value
.
o
;
Py_XDECREF
(
prev
);
if
(
closure
)
{
assert
(
0
&&
"check refcounting"
);
created_closure
->
elts
[
scope_info
->
getClosureOffset
(
name
)]
=
value
.
o
;
}
}
...
...
@@ -524,7 +549,7 @@ void ASTInterpreter::doStore(AST_expr* node, Value value) {
}
Value
ASTInterpreter
::
getNone
()
{
return
Value
(
None
,
jit
?
jit
->
imm
(
None
)
:
NULL
);
return
Value
(
incref
(
None
)
,
jit
?
jit
->
imm
(
None
)
:
NULL
);
}
Value
ASTInterpreter
::
visit_unaryop
(
AST_UnaryOp
*
node
)
{
...
...
@@ -538,7 +563,10 @@ Value ASTInterpreter::visit_unaryop(AST_UnaryOp* node) {
Value
ASTInterpreter
::
visit_binop
(
AST_BinOp
*
node
)
{
Value
left
=
visit_expr
(
node
->
left
);
Value
right
=
visit_expr
(
node
->
right
);
return
doBinOp
(
node
,
left
,
right
,
node
->
op_type
,
BinExpType
::
BinOp
);
Value
r
=
doBinOp
(
node
,
left
,
right
,
node
->
op_type
,
BinExpType
::
BinOp
);
Py_DECREF
(
left
.
o
);
Py_DECREF
(
right
.
o
);
return
r
;
}
Value
ASTInterpreter
::
visit_slice
(
AST_slice
*
node
)
{
...
...
@@ -599,6 +627,8 @@ Value ASTInterpreter::visit_branch(AST_Branch* node) {
next_block
=
node
->
iftrue
;
else
next_block
=
node
->
iffalse
;
// TODO could potentially avoid doing this if we skip the incref in NONZERO
Py_DECREF
(
v
.
o
);
if
(
jit
)
{
jit
->
emitJump
(
next_block
);
...
...
@@ -812,7 +842,10 @@ Value ASTInterpreter::visit_augBinOp(AST_AugBinOp* node) {
Value
left
=
visit_expr
(
node
->
left
);
Value
right
=
visit_expr
(
node
->
right
);
return
doBinOp
(
node
,
left
,
right
,
node
->
op_type
,
BinExpType
::
AugBinOp
);
Value
r
=
doBinOp
(
node
,
left
,
right
,
node
->
op_type
,
BinExpType
::
AugBinOp
);
Py_DECREF
(
left
.
o
);
Py_DECREF
(
right
.
o
);
return
r
;
}
Value
ASTInterpreter
::
visit_langPrimitive
(
AST_LangPrimitive
*
node
)
{
...
...
@@ -879,6 +912,7 @@ Value ASTInterpreter::visit_langPrimitive(AST_LangPrimitive* node) {
assert
(
node
->
args
.
size
()
==
1
);
Value
obj
=
visit_expr
(
node
->
args
[
0
]);
v
=
Value
(
boxBool
(
nonzero
(
obj
.
o
)),
jit
?
jit
->
emitNonzero
(
obj
)
:
NULL
);
Py_DECREF
(
obj
.
o
);
}
else
if
(
node
->
opcode
==
AST_LangPrimitive
::
SET_EXC_INFO
)
{
assert
(
node
->
args
.
size
()
==
3
);
...
...
@@ -1235,8 +1269,7 @@ Value ASTInterpreter::visit_assign(AST_Assign* node) {
assert
(
node
->
targets
.
size
()
==
1
&&
"cfg should have lowered it to a single target"
);
Value
v
=
visit_expr
(
node
->
value
);
for
(
AST_expr
*
e
:
node
->
targets
)
doStore
(
e
,
v
);
doStore
(
node
->
targets
[
0
],
v
);
return
Value
();
}
...
...
@@ -1249,9 +1282,9 @@ Value ASTInterpreter::visit_print(AST_Print* node) {
jit
->
emitPrint
(
dest
,
var
,
node
->
nl
);
if
(
node
->
dest
)
printHelper
(
dest
.
o
,
var
.
o
,
node
->
nl
);
printHelper
(
autoDecref
(
dest
.
o
),
autoXDecref
(
var
.
o
)
,
node
->
nl
);
else
printHelper
(
getSysStdout
(),
var
.
o
,
node
->
nl
);
printHelper
(
getSysStdout
(),
autoXDecref
(
var
.
o
)
,
node
->
nl
);
return
Value
();
}
...
...
@@ -1273,7 +1306,10 @@ Value ASTInterpreter::visit_compare(AST_Compare* node) {
RELEASE_ASSERT
(
node
->
comparators
.
size
()
==
1
,
"not implemented"
);
Value
left
=
visit_expr
(
node
->
left
);
Value
right
=
visit_expr
(
node
->
comparators
[
0
]);
return
doBinOp
(
node
,
left
,
right
,
node
->
ops
[
0
],
BinExpType
::
Compare
);
Value
r
=
doBinOp
(
node
,
left
,
right
,
node
->
ops
[
0
],
BinExpType
::
Compare
);
Py_DECREF
(
left
.
o
);
Py_DECREF
(
right
.
o
);
return
r
;
}
Value
ASTInterpreter
::
visit_expr
(
AST_expr
*
node
)
{
...
...
@@ -1424,6 +1460,7 @@ Value ASTInterpreter::visit_num(AST_Num* node) {
o
=
parent_module
->
getPureImaginaryConstant
(
node
->
n_float
);
}
else
RELEASE_ASSERT
(
0
,
"not implemented"
);
Py_INCREF
(
o
);
return
Value
(
o
,
jit
?
jit
->
imm
(
o
)
:
NULL
);
}
...
...
@@ -1499,10 +1536,12 @@ Value ASTInterpreter::visit_name(AST_Name* node) {
if
(
jit
)
v
.
var
=
jit
->
emitGetGlobal
(
globals
,
node
->
id
.
getBox
());
assert
(
0
&&
"check refcounting"
);
v
.
o
=
getGlobal
(
globals
,
node
->
id
.
getBox
());
return
v
;
}
case
ScopeInfo
:
:
VarScopeType
::
DEREF
:
{
assert
(
0
&&
"check refcounting"
);
return
Value
(
ASTInterpreterJitInterface
::
derefHelper
(
this
,
node
->
id
),
jit
?
jit
->
emitDeref
(
node
->
id
)
:
NULL
);
}
...
...
@@ -1510,6 +1549,7 @@ Value ASTInterpreter::visit_name(AST_Name* node) {
case
ScopeInfo
:
:
VarScopeType
::
CLOSURE
:
{
Value
v
;
if
(
jit
)
{
assert
(
0
&&
"check refcounting"
);
bool
is_live
=
false
;
if
(
node
->
lookup_type
==
ScopeInfo
::
VarScopeType
::
FAST
)
is_live
=
source_info
->
getLiveness
()
->
isLiveAtEnd
(
node
->
id
,
current_block
);
...
...
@@ -1525,6 +1565,7 @@ Value ASTInterpreter::visit_name(AST_Name* node) {
assert
(
getSymVRegMap
()[
node
->
id
]
==
node
->
vreg
);
Box
*
val
=
vregs
[
node
->
vreg
];
if
(
val
)
{
Py_INCREF
(
val
);
v
.
o
=
val
;
return
v
;
}
...
...
@@ -1533,6 +1574,7 @@ Value ASTInterpreter::visit_name(AST_Name* node) {
RELEASE_ASSERT
(
0
,
"should be unreachable"
);
}
case
ScopeInfo
:
:
VarScopeType
::
NAME
:
{
assert
(
0
&&
"check refcounting"
);
Value
v
;
if
(
jit
)
v
.
var
=
jit
->
emitGetBoxedLocal
(
node
->
id
.
getBox
());
...
...
@@ -1566,6 +1608,7 @@ Value ASTInterpreter::visit_list(AST_List* node) {
}
Value
ASTInterpreter
::
visit_tuple
(
AST_Tuple
*
node
)
{
return
getNone
();
llvm
::
SmallVector
<
RewriterVar
*
,
8
>
items
;
BoxedTuple
*
rtn
=
BoxedTuple
::
create
(
node
->
elts
.
size
());
...
...
@@ -1804,7 +1847,7 @@ Box* astInterpretFunction(FunctionMetadata* md, Box* closure, Box* generator, Bo
interpreter
.
initArguments
((
BoxedClosure
*
)
closure
,
(
BoxedGenerator
*
)
generator
,
arg1
,
arg2
,
arg3
,
args
);
Box
*
v
=
ASTInterpreter
::
execute
(
interpreter
);
return
v
?
v
:
None
;
return
v
?
v
:
incref
(
None
)
;
}
Box
*
astInterpretFunctionEval
(
FunctionMetadata
*
md
,
Box
*
globals
,
Box
*
boxedLocals
)
{
...
...
src/codegen/irgen/hooks.cpp
View file @
51b685f5
...
...
@@ -323,10 +323,11 @@ void compileAndRunModule(AST_Module* m, BoxedModule* bm) {
FutureFlags
future_flags
=
getFutureFlags
(
m
->
body
,
fn
);
ScopingAnalysis
*
scoping
=
new
ScopingAnalysis
(
m
,
true
);
std
::
unique_ptr
<
SourceInfo
>
si
(
new
SourceInfo
(
bm
,
scoping
,
future_flags
,
m
,
m
->
body
,
boxString
(
fn
)));
auto
fn_str
=
getStaticString
(
fn
);
// XXX this is not a static string
std
::
unique_ptr
<
SourceInfo
>
si
(
new
SourceInfo
(
bm
,
scoping
,
future_flags
,
m
,
m
->
body
,
fn_str
));
static
BoxedString
*
doc_str
=
getStaticString
(
"__doc__"
);
bm
->
setattr
(
doc_str
,
si
->
getDocString
(
),
NULL
);
bm
->
setattr
(
doc_str
,
autoDecref
(
si
->
getDocString
()
),
NULL
);
static
BoxedString
*
builtins_str
=
getStaticString
(
"__builtins__"
);
if
(
!
bm
->
hasattr
(
builtins_str
))
...
...
@@ -338,6 +339,7 @@ void compileAndRunModule(AST_Module* m, BoxedModule* bm) {
UNAVOIDABLE_STAT_TIMER
(
t0
,
"us_timer_interpreted_module_toplevel"
);
Box
*
r
=
astInterpretFunction
(
md
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
);
assert
(
r
==
None
);
Py_DECREF
(
r
);
}
Box
*
evalOrExec
(
FunctionMetadata
*
md
,
Box
*
globals
,
Box
*
boxedLocals
)
{
...
...
src/core/stringpool.cpp
View file @
51b685f5
...
...
@@ -20,7 +20,8 @@ namespace pyston {
InternedString
InternedStringPool
::
get
(
llvm
::
StringRef
arg
)
{
// HACK: should properly track this liveness:
BoxedString
*
s
=
internStringImmortal
(
arg
);
// XXX it's not a static string
BoxedString
*
s
=
getStaticString
(
arg
);
#ifndef NDEBUG
return
InternedString
(
s
,
this
);
...
...
src/runtime/objmodel.cpp
View file @
51b685f5
...
...
@@ -164,13 +164,14 @@ extern "C" bool softspace(Box* b, bool newval) {
r
=
0
;
}
else
{
r
=
nonzero
(
gotten
);
Py_DECREF
(
gotten
);
}
}
catch
(
ExcInfo
e
)
{
r
=
0
;
}
try
{
setattr
(
b
,
softspace_str
,
boxInt
(
newval
));
setattr
(
b
,
softspace_str
,
autoDecref
(
boxInt
(
newval
)
));
}
catch
(
ExcInfo
e
)
{
r
=
0
;
}
...
...
@@ -193,11 +194,12 @@ extern "C" void printHelper(Box* dest, Box* var, bool nl) {
callattrInternal
<
CXX
,
NOT_REWRITABLE
>
(
dest
,
write_str
,
CLASS_OR_INST
,
0
,
ArgPassSpec
(
1
),
space_str
,
0
,
0
,
0
,
0
);
Box
*
str_or_unicode_var
=
(
var
->
cls
==
unicode_cls
)
?
var
:
str
(
var
);
Box
*
str_or_unicode_var
=
(
var
->
cls
==
unicode_cls
)
?
incref
(
var
)
:
str
(
var
);
Box
*
write_rtn
=
callattrInternal
<
CXX
,
NOT_REWRITABLE
>
(
dest
,
write_str
,
CLASS_OR_INST
,
0
,
ArgPassSpec
(
1
),
str_or_unicode_var
,
0
,
0
,
0
,
0
);
autoDecref
(
str_or_unicode_var
)
,
0
,
0
,
0
,
0
);
if
(
!
write_rtn
)
raiseAttributeError
(
dest
,
write_str
->
s
());
Py_DECREF
(
write_rtn
);
}
if
(
nl
)
{
...
...
@@ -205,6 +207,8 @@ extern "C" void printHelper(Box* dest, Box* var, bool nl) {
newline_str
,
0
,
0
,
0
,
0
);
if
(
!
write_rtn
)
raiseAttributeError
(
dest
,
write_str
->
s
());
Py_DECREF
(
write_rtn
);
if
(
!
var
)
softspace
(
dest
,
false
);
}
...
...
src/runtime/types.cpp
View file @
51b685f5
...
...
@@ -3427,21 +3427,24 @@ int BoxedModule::traverse(Box* _m, visitproc visit, void* arg) noexcept {
return
0
;
}
template
<
typename
CM
>
void
clearContiguousMap
(
CM
&
cm
)
{
for
(
auto
&&
p
:
cm
)
{
Py_DECREF
(
cm
.
getMapped
(
p
.
second
));
}
cm
.
~
ContiguousMap
();
}
int
BoxedModule
::
clear
(
Box
*
b
)
noexcept
{
BoxedModule
*
self
=
static_cast
<
BoxedModule
*>
(
b
);
self
->
clearAttrs
();
assert
(
!
self
->
str_constants
.
size
());
assert
(
!
self
->
unicode_constants
.
size
());
for
(
auto
p
:
self
->
int_constants
)
{
Py_DECREF
(
self
->
int_constants
.
getMapped
(
p
.
second
));
}
self
->
int_constants
.
~
ContiguousMap
();
assert
(
!
self
->
float_constants
.
size
());
assert
(
!
self
->
imaginary_constants
.
size
());
assert
(
!
self
->
long_constants
.
size
());
clearContiguousMap
(
self
->
str_constants
);
clearContiguousMap
(
self
->
unicode_constants
);
clearContiguousMap
(
self
->
int_constants
);
clearContiguousMap
(
self
->
float_constants
);
clearContiguousMap
(
self
->
imaginary_constants
);
clearContiguousMap
(
self
->
long_constants
);
assert
(
!
self
->
keep_alive
.
size
());
return
0
;
...
...
src/runtime/types.h
View file @
51b685f5
...
...
@@ -70,7 +70,7 @@ void setupSysEnd();
BORROWED
(
BoxedDict
*
)
getSysModulesDict
();
BORROWED
(
BoxedList
*
)
getSysPath
();
extern
"C"
B
ox
*
getSysStdout
();
extern
"C"
B
ORROWED
(
Box
*
)
getSysStdout
();
extern
"C"
BoxedTuple
*
EmptyTuple
;
extern
"C"
BoxedString
*
EmptyString
;
...
...
@@ -389,6 +389,9 @@ public:
template
<
typename
B
,
bool
Nullable
=
false
>
DecrefHandle
<
B
,
Nullable
>
autoDecref
(
B
*
b
)
{
return
DecrefHandle
<
B
,
Nullable
>
(
b
);
}
template
<
typename
B
>
DecrefHandle
<
B
,
true
>
autoXDecref
(
B
*
b
)
{
return
DecrefHandle
<
B
,
true
>
(
b
);
}
template
<
typename
B
>
B
*
incref
(
B
*
b
)
{
Py_INCREF
(
b
);
...
...
@@ -946,12 +949,12 @@ public:
BoxedModule
()
{}
// noop constructor to disable zero-initialization of cls
std
::
string
name
();
B
oxedString
*
getStringConstant
(
llvm
::
StringRef
ast_str
,
bool
intern
=
false
);
B
ox
*
getUnicodeConstant
(
llvm
::
StringRef
ast_str
);
B
oxedInt
*
getIntConstant
(
int64_t
n
);
B
oxedFloat
*
getFloatConstant
(
double
d
);
B
ox
*
getPureImaginaryConstant
(
double
d
);
B
ox
*
getLongConstant
(
llvm
::
StringRef
s
);
B
ORROWED
(
BoxedString
*
)
getStringConstant
(
llvm
::
StringRef
ast_str
,
bool
intern
=
false
);
B
ORROWED
(
Box
*
)
getUnicodeConstant
(
llvm
::
StringRef
ast_str
);
B
ORROWED
(
BoxedInt
*
)
getIntConstant
(
int64_t
n
);
B
ORROWED
(
BoxedFloat
*
)
getFloatConstant
(
double
d
);
B
ORROWED
(
Box
*
)
getPureImaginaryConstant
(
double
d
);
B
ORROWED
(
Box
*
)
getLongConstant
(
llvm
::
StringRef
s
);
static
void
dealloc
(
Box
*
b
)
noexcept
;
static
int
traverse
(
Box
*
self
,
visitproc
visit
,
void
*
arg
)
noexcept
;
...
...
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