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
a9813b80
Commit
a9813b80
authored
Jun 26, 2015
by
Kevin Modzelewski
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #648 from kmod/perf6
reduce api conversions
parents
79505a1a
76c861ad
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
262 additions
and
139 deletions
+262
-139
microbenchmarks/itertools_chain_bench.py
microbenchmarks/itertools_chain_bench.py
+13
-0
microbenchmarks/loop.py
microbenchmarks/loop.py
+5
-0
microbenchmarks/unicode_ctor_bench.py
microbenchmarks/unicode_ctor_bench.py
+6
-0
src/asm_writing/rewriter.cpp
src/asm_writing/rewriter.cpp
+4
-1
src/capi/typeobject.cpp
src/capi/typeobject.cpp
+2
-2
src/capi/typeobject.h
src/capi/typeobject.h
+2
-0
src/capi/types.h
src/capi/types.h
+3
-54
src/core/types.h
src/core/types.h
+0
-1
src/runtime/builtin_modules/builtins.cpp
src/runtime/builtin_modules/builtins.cpp
+1
-1
src/runtime/capi.cpp
src/runtime/capi.cpp
+95
-37
src/runtime/descr.cpp
src/runtime/descr.cpp
+3
-0
src/runtime/ics.h
src/runtime/ics.h
+1
-1
src/runtime/iterators.cpp
src/runtime/iterators.cpp
+6
-17
src/runtime/iterobject.cpp
src/runtime/iterobject.cpp
+7
-13
src/runtime/list.cpp
src/runtime/list.cpp
+1
-1
src/runtime/objmodel.cpp
src/runtime/objmodel.cpp
+17
-5
src/runtime/stacktrace.cpp
src/runtime/stacktrace.cpp
+4
-0
src/runtime/types.cpp
src/runtime/types.cpp
+92
-6
No files found.
microbenchmarks/itertools_chain_bench.py
0 → 100644
View file @
a9813b80
# simple benchmark to test iteration over extension objects
import
itertools
def
f
(
c
):
for
i
in
c
:
pass
l
=
[]
for
i
in
xrange
(
100
):
l
.
append
(
itertools
.
chain
(
*
[
range
(
500
)
for
j
in
xrange
(
500
)]))
c
=
itertools
.
chain
(
*
l
)
f
(
c
)
microbenchmarks/loop.py
0 → 100644
View file @
a9813b80
def
f
(
n
):
for
i
in
xrange
(
n
):
pass
f
(
100000000
)
microbenchmarks/unicode_ctor_bench.py
0 → 100644
View file @
a9813b80
def
f
():
u
=
u"a"
*
100
c
=
unicode
for
i
in
xrange
(
2000000
):
c
(
u
)
f
()
src/asm_writing/rewriter.cpp
View file @
a9813b80
...
@@ -743,10 +743,13 @@ void Rewriter::_call(RewriterVar* result, bool has_side_effects, void* func_addr
...
@@ -743,10 +743,13 @@ void Rewriter::_call(RewriterVar* result, bool has_side_effects, void* func_addr
if
(
has_side_effects
)
{
if
(
has_side_effects
)
{
// We need some fixed amount of space at the beginning of the IC that we can use to invalidate
// We need some fixed amount of space at the beginning of the IC that we can use to invalidate
// it by writing a jmp.
// it by writing a jmp.
//
FIXME
this check is conservative, since actually we just have to verify that the return
//
TODO
this check is conservative, since actually we just have to verify that the return
// address is at least IC_INVALDITION_HEADER_SIZE bytes past the beginning, but we're
// address is at least IC_INVALDITION_HEADER_SIZE bytes past the beginning, but we're
// checking based on the beginning of the call. I think the load+call might actually
// checking based on the beginning of the call. I think the load+call might actually
// always larger than the invalidation jmp.
// always larger than the invalidation jmp.
while
(
assembler
->
bytesWritten
()
<
IC_INVALDITION_HEADER_SIZE
)
assembler
->
nop
();
assert
(
assembler
->
bytesWritten
()
>=
IC_INVALDITION_HEADER_SIZE
);
assert
(
assembler
->
bytesWritten
()
>=
IC_INVALDITION_HEADER_SIZE
);
}
}
...
...
src/capi/typeobject.cpp
View file @
a9813b80
...
@@ -827,7 +827,7 @@ static PyObject* slot_tp_iter(PyObject* self) noexcept {
...
@@ -827,7 +827,7 @@ static PyObject* slot_tp_iter(PyObject* self) noexcept {
return
PySeqIter_New
(
self
);
return
PySeqIter_New
(
self
);
}
}
static
PyObject
*
slot_tp_iternext
(
PyObject
*
self
)
noexcept
{
/* Pyston change: static */
PyObject
*
slot_tp_iternext
(
PyObject
*
self
)
noexcept
{
STAT_TIMER
(
t0
,
"us_timer_slot_tpiternext"
,
SLOT_AVOIDABILITY
(
self
));
STAT_TIMER
(
t0
,
"us_timer_slot_tpiternext"
,
SLOT_AVOIDABILITY
(
self
));
static
PyObject
*
next_str
;
static
PyObject
*
next_str
;
...
@@ -957,7 +957,7 @@ static PyObject* slot_tp_getattr_hook(PyObject* self, PyObject* name) noexcept {
...
@@ -957,7 +957,7 @@ static PyObject* slot_tp_getattr_hook(PyObject* self, PyObject* name) noexcept {
return
res
;
return
res
;
}
}
static
PyObject
*
slot_tp_new
(
PyTypeObject
*
self
,
PyObject
*
args
,
PyObject
*
kwds
)
noexcept
{
/* Pyston change: static */
PyObject
*
slot_tp_new
(
PyTypeObject
*
self
,
PyObject
*
args
,
PyObject
*
kwds
)
noexcept
{
STAT_TIMER
(
t0
,
"us_timer_slot_tpnew"
,
SLOT_AVOIDABILITY
(
self
));
STAT_TIMER
(
t0
,
"us_timer_slot_tpnew"
,
SLOT_AVOIDABILITY
(
self
));
try
{
try
{
...
...
src/capi/typeobject.h
View file @
a9813b80
...
@@ -35,6 +35,8 @@ PyObject* mro_external(PyObject* self) noexcept;
...
@@ -35,6 +35,8 @@ PyObject* mro_external(PyObject* self) noexcept;
int
type_set_bases
(
PyTypeObject
*
type
,
PyObject
*
value
,
void
*
context
)
noexcept
;
int
type_set_bases
(
PyTypeObject
*
type
,
PyObject
*
value
,
void
*
context
)
noexcept
;
PyObject
*
slot_tp_richcompare
(
PyObject
*
self
,
PyObject
*
other
,
int
op
)
noexcept
;
PyObject
*
slot_tp_richcompare
(
PyObject
*
self
,
PyObject
*
other
,
int
op
)
noexcept
;
PyObject
*
slot_tp_iternext
(
PyObject
*
self
)
noexcept
;
PyObject
*
slot_tp_new
(
PyTypeObject
*
self
,
PyObject
*
args
,
PyObject
*
kwds
)
noexcept
;
}
}
#endif
#endif
src/capi/types.h
View file @
a9813b80
...
@@ -42,57 +42,9 @@ public:
...
@@ -42,57 +42,9 @@ public:
return
boxString
(
self
->
method_def
->
ml_name
);
return
boxString
(
self
->
method_def
->
ml_name
);
}
}
static
Box
*
__call__
(
BoxedCApiFunction
*
self
,
BoxedTuple
*
varargs
,
BoxedDict
*
kwargs
)
{
static
Box
*
__call__
(
BoxedCApiFunction
*
self
,
BoxedTuple
*
varargs
,
BoxedDict
*
kwargs
);
STAT_TIMER
(
t0
,
"us_timer_boxedcapifunction__call__"
,
(
self
->
cls
->
is_user_defined
?
10
:
20
));
static
Box
*
tppCall
(
Box
*
_self
,
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
assert
(
self
->
cls
==
capifunc_cls
);
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
);
assert
(
varargs
->
cls
==
tuple_cls
);
assert
(
kwargs
->
cls
==
dict_cls
);
threading
::
GLPromoteRegion
_gil_lock
;
Box
*
rtn
;
int
flags
=
self
->
method_def
->
ml_flags
;
auto
func
=
self
->
method_def
->
ml_meth
;
if
(
flags
==
METH_VARARGS
)
{
assert
(
kwargs
->
d
.
size
()
==
0
);
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
varargs
);
}
else
if
(
flags
==
(
METH_VARARGS
|
METH_KEYWORDS
))
{
rtn
=
(
Box
*
)((
PyCFunctionWithKeywords
)
func
)(
self
->
passthrough
,
varargs
,
kwargs
);
}
else
if
(
flags
==
METH_NOARGS
)
{
assert
(
kwargs
->
d
.
size
()
==
0
);
assert
(
varargs
->
size
()
==
0
);
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
NULL
);
}
else
if
(
flags
==
METH_O
)
{
if
(
kwargs
->
d
.
size
()
!=
0
)
{
raiseExcHelper
(
TypeError
,
"%s() takes no keyword arguments"
,
self
->
method_def
->
ml_name
);
}
if
(
varargs
->
size
()
!=
1
)
{
raiseExcHelper
(
TypeError
,
"%s() takes exactly one argument (%d given)"
,
self
->
method_def
->
ml_name
,
varargs
->
size
());
}
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
varargs
->
elts
[
0
]);
}
else
if
(
flags
==
METH_OLDARGS
)
{
/* the really old style */
if
(
kwargs
==
NULL
||
PyDict_Size
(
kwargs
)
==
0
)
{
int
size
=
PyTuple_GET_SIZE
(
varargs
);
Box
*
arg
=
varargs
;
if
(
size
==
1
)
arg
=
PyTuple_GET_ITEM
(
varargs
,
0
);
else
if
(
size
==
0
)
arg
=
NULL
;
rtn
=
func
(
self
->
passthrough
,
arg
);
}
else
{
raiseExcHelper
(
TypeError
,
"%.200s() takes no keyword arguments"
,
self
->
method_def
->
ml_name
);
}
}
else
{
RELEASE_ASSERT
(
0
,
"0x%x"
,
flags
);
}
checkAndThrowCAPIException
();
assert
(
rtn
&&
"should have set + thrown an exception!"
);
return
rtn
;
}
static
Box
*
getname
(
Box
*
b
,
void
*
)
{
static
Box
*
getname
(
Box
*
b
,
void
*
)
{
RELEASE_ASSERT
(
b
->
cls
==
capifunc_cls
,
""
);
RELEASE_ASSERT
(
b
->
cls
==
capifunc_cls
,
""
);
...
@@ -102,9 +54,6 @@ public:
...
@@ -102,9 +54,6 @@ public:
return
None
;
return
None
;
}
}
static
Box
*
callInternal
(
BoxedFunctionBase
*
func
,
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
);
static
void
gcHandler
(
GCVisitor
*
v
,
Box
*
_o
)
{
static
void
gcHandler
(
GCVisitor
*
v
,
Box
*
_o
)
{
assert
(
_o
->
cls
==
capifunc_cls
);
assert
(
_o
->
cls
==
capifunc_cls
);
BoxedCApiFunction
*
o
=
static_cast
<
BoxedCApiFunction
*>
(
_o
);
BoxedCApiFunction
*
o
=
static_cast
<
BoxedCApiFunction
*>
(
_o
);
...
...
src/core/types.h
View file @
a9813b80
...
@@ -531,7 +531,6 @@ public:
...
@@ -531,7 +531,6 @@ public:
BoxedString
*
reprICAsString
();
BoxedString
*
reprICAsString
();
bool
nonzeroIC
();
bool
nonzeroIC
();
Box
*
hasnextOrNullIC
();
Box
*
hasnextOrNullIC
();
Box
*
nextIC
();
friend
class
AttrWrapper
;
friend
class
AttrWrapper
;
};
};
...
...
src/runtime/builtin_modules/builtins.cpp
View file @
a9813b80
...
@@ -248,7 +248,7 @@ Box* open(Box* arg1, Box* arg2, Box* arg3) {
...
@@ -248,7 +248,7 @@ Box* open(Box* arg1, Box* arg2, Box* arg3) {
extern
"C"
Box
*
chr
(
Box
*
arg
)
{
extern
"C"
Box
*
chr
(
Box
*
arg
)
{
i64
n
=
PyInt_AsLong
(
arg
);
i64
n
=
PyInt_AsLong
(
arg
);
if
(
n
==
-
1
&&
PyErr_Occurred
())
if
(
n
==
-
1
&&
PyErr_Occurred
())
raiseExcHelper
(
TypeError
,
"an integer is required"
);
throwCAPIException
(
);
if
(
n
<
0
||
n
>=
256
)
{
if
(
n
<
0
||
n
>=
256
)
{
raiseExcHelper
(
ValueError
,
"chr() arg not in range(256)"
);
raiseExcHelper
(
ValueError
,
"chr() arg not in range(256)"
);
...
...
src/runtime/capi.cpp
View file @
a9813b80
...
@@ -633,25 +633,6 @@ extern "C" int PyObject_Print(PyObject* obj, FILE* fp, int flags) noexcept {
...
@@ -633,25 +633,6 @@ extern "C" int PyObject_Print(PyObject* obj, FILE* fp, int flags) noexcept {
return
internal_print
(
obj
,
fp
,
flags
,
0
);
return
internal_print
(
obj
,
fp
,
flags
,
0
);
};
};
extern
"C"
PyObject
*
PyIter_Next
(
PyObject
*
iter
)
noexcept
{
try
{
Box
*
hasnext
=
iter
->
hasnextOrNullIC
();
if
(
hasnext
)
{
if
(
hasnext
->
nonzeroIC
())
return
iter
->
nextIC
();
else
return
NULL
;
}
else
{
return
iter
->
nextIC
();
}
}
catch
(
ExcInfo
e
)
{
if
(
e
.
matches
(
StopIteration
))
return
NULL
;
setCAPIException
(
e
);
}
return
NULL
;
}
extern
"C"
int
PyCallable_Check
(
PyObject
*
x
)
noexcept
{
extern
"C"
int
PyCallable_Check
(
PyObject
*
x
)
noexcept
{
if
(
x
==
NULL
)
if
(
x
==
NULL
)
return
0
;
return
0
;
...
@@ -1457,29 +1438,106 @@ extern "C" char* PyModule_GetFilename(PyObject* m) noexcept {
...
@@ -1457,29 +1438,106 @@ extern "C" char* PyModule_GetFilename(PyObject* m) noexcept {
return
PyString_AsString
(
fileobj
);
return
PyString_AsString
(
fileobj
);
}
}
Box
*
BoxedCApiFunction
::
callInternal
(
BoxedFunctionBase
*
func
,
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
BoxedCApiFunction
::
__call__
(
BoxedCApiFunction
*
self
,
BoxedTuple
*
varargs
,
BoxedDict
*
kwargs
)
{
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
Box
**
args
,
STAT_TIMER
(
t0
,
"us_timer_boxedcapifunction__call__"
,
(
self
->
cls
->
is_user_defined
?
10
:
20
));
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
assert
(
self
->
cls
==
capifunc_cls
);
if
(
argspec
!=
ArgPassSpec
(
2
))
assert
(
varargs
->
cls
==
tuple_cls
);
return
callFunc
(
func
,
rewrite_args
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_name
s
);
assert
(
kwargs
->
cls
==
dict_cl
s
);
assert
(
arg1
->
cls
==
capifunc_cls
);
// Kind of silly to have asked callFunc to rearrange the arguments for us, just to pass things
BoxedCApiFunction
*
capifunc
=
static_cast
<
BoxedCApiFunction
*>
(
arg1
);
// off to tppCall, but this case should be very uncommon (people explicitly asking for __call__)
if
(
capifunc
->
method_def
->
ml_flags
!=
METH_O
)
return
callFunc
(
func
,
rewrite_args
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_names
);
return
BoxedCApiFunction
::
tppCall
(
self
,
NULL
,
ArgPassSpec
(
0
,
0
,
true
,
true
),
varargs
,
kwargs
,
NULL
,
NULL
,
NULL
);
}
Box
*
BoxedCApiFunction
::
tppCall
(
Box
*
_self
,
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
STAT_TIMER
(
t0
,
"us_timer_boxedcapifunction__call__"
,
10
);
assert
(
_self
->
cls
==
capifunc_cls
);
BoxedCApiFunction
*
self
=
static_cast
<
BoxedCApiFunction
*>
(
_self
);
if
(
rewrite_args
)
{
if
(
rewrite_args
)
{
rewrite_args
->
arg1
->
addGuard
((
intptr_t
)
arg1
);
rewrite_args
->
obj
->
addGuard
((
intptr_t
)
self
);
RewriterVar
*
r_passthrough
=
rewrite_args
->
arg1
->
getAttr
(
offsetof
(
BoxedCApiFunction
,
passthrough
));
}
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
capifunc
->
method_def
->
ml_meth
,
r_passthrough
,
int
flags
=
self
->
method_def
->
ml_flags
;
auto
func
=
self
->
method_def
->
ml_meth
;
ParamReceiveSpec
paramspec
(
0
,
0
,
true
,
false
);
if
(
flags
==
METH_VARARGS
)
{
paramspec
=
ParamReceiveSpec
(
0
,
0
,
true
,
false
);
}
else
if
(
flags
==
(
METH_VARARGS
|
METH_KEYWORDS
))
{
paramspec
=
ParamReceiveSpec
(
0
,
0
,
true
,
true
);
}
else
if
(
flags
==
METH_NOARGS
)
{
paramspec
=
ParamReceiveSpec
(
0
,
0
,
false
,
false
);
}
else
if
(
flags
==
METH_O
)
{
paramspec
=
ParamReceiveSpec
(
1
,
0
,
false
,
false
);
}
else
if
(
flags
==
METH_OLDARGS
)
{
paramspec
=
ParamReceiveSpec
(
1
,
0
,
false
,
false
);
}
else
{
RELEASE_ASSERT
(
0
,
"0x%x"
,
flags
);
}
Box
*
oarg1
=
NULL
;
Box
*
oarg2
=
NULL
;
Box
*
oarg3
=
NULL
;
Box
**
oargs
=
NULL
;
bool
rewrite_success
=
false
;
rearrangeArguments
(
paramspec
,
NULL
,
self
->
method_def
->
ml_name
,
NULL
,
rewrite_args
,
rewrite_success
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_names
,
oarg1
,
oarg2
,
oarg3
,
args
);
if
(
!
rewrite_success
)
rewrite_args
=
NULL
;
RewriterVar
*
r_passthrough
;
if
(
rewrite_args
)
r_passthrough
=
rewrite_args
->
rewriter
->
loadConst
((
intptr_t
)
self
->
passthrough
,
Location
::
forArg
(
0
));
Box
*
rtn
;
if
(
flags
==
METH_VARARGS
)
{
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
oarg1
);
if
(
rewrite_args
)
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
func
,
r_passthrough
,
rewrite_args
->
arg1
);
}
else
if
(
flags
==
(
METH_VARARGS
|
METH_KEYWORDS
))
{
rtn
=
(
Box
*
)((
PyCFunctionWithKeywords
)
func
)(
self
->
passthrough
,
oarg1
,
oarg2
);
if
(
rewrite_args
)
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
func
,
r_passthrough
,
rewrite_args
->
arg1
,
rewrite_args
->
arg2
);
rewrite_args
->
arg2
);
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
checkAndThrowCAPIException
);
}
else
if
(
flags
==
METH_NOARGS
)
{
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
NULL
);
if
(
rewrite_args
)
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
func
,
r_passthrough
,
rewrite_args
->
rewriter
->
loadConst
(
0
,
Location
::
forArg
(
1
)));
}
else
if
(
flags
==
METH_O
)
{
rtn
=
(
Box
*
)
func
(
self
->
passthrough
,
oarg1
);
if
(
rewrite_args
)
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
func
,
r_passthrough
,
rewrite_args
->
arg1
);
}
else
if
(
flags
==
METH_OLDARGS
)
{
/* the really old style */
rewrite_args
=
NULL
;
int
size
=
PyTuple_GET_SIZE
(
oarg1
);
Box
*
arg
=
oarg1
;
if
(
size
==
1
)
arg
=
PyTuple_GET_ITEM
(
oarg1
,
0
);
else
if
(
size
==
0
)
arg
=
NULL
;
rtn
=
func
(
self
->
passthrough
,
arg
);
}
else
{
RELEASE_ASSERT
(
0
,
"0x%x"
,
flags
);
}
if
(
rewrite_args
)
{
rewrite_args
->
rewriter
->
call
(
false
,
(
void
*
)
checkAndThrowCAPIException
);
rewrite_args
->
out_success
=
true
;
rewrite_args
->
out_success
=
true
;
}
}
Box
*
r
=
capifunc
->
method_def
->
ml_meth
(
capifunc
->
passthrough
,
arg2
);
checkAndThrowCAPIException
();
checkAndThrowCAPIException
();
assert
(
r
);
assert
(
r
tn
&&
"should have set + thrown an exception!"
);
return
r
;
return
r
tn
;
}
}
/* extension modules might be compiled with GC support so these
/* extension modules might be compiled with GC support so these
...
@@ -1546,8 +1604,8 @@ void setupCAPI() {
...
@@ -1546,8 +1604,8 @@ void setupCAPI() {
new
BoxedFunction
(
boxRTFunction
((
void
*
)
BoxedCApiFunction
::
__repr__
,
UNKNOWN
,
1
)));
new
BoxedFunction
(
boxRTFunction
((
void
*
)
BoxedCApiFunction
::
__repr__
,
UNKNOWN
,
1
)));
auto
capi_call
=
new
BoxedFunction
(
boxRTFunction
((
void
*
)
BoxedCApiFunction
::
__call__
,
UNKNOWN
,
1
,
0
,
true
,
true
));
auto
capi_call
=
new
BoxedFunction
(
boxRTFunction
((
void
*
)
BoxedCApiFunction
::
__call__
,
UNKNOWN
,
1
,
0
,
true
,
true
));
capi_call
->
f
->
internal_callable
=
BoxedCApiFunction
::
callInternal
;
capifunc_cls
->
giveAttr
(
"__call__"
,
capi_call
);
capifunc_cls
->
giveAttr
(
"__call__"
,
capi_call
);
capifunc_cls
->
tpp_call
=
BoxedCApiFunction
::
tppCall
;
capifunc_cls
->
giveAttr
(
"__name__"
,
capifunc_cls
->
giveAttr
(
"__name__"
,
new
(
pyston_getset_cls
)
BoxedGetsetDescriptor
(
BoxedCApiFunction
::
getname
,
NULL
,
NULL
));
new
(
pyston_getset_cls
)
BoxedGetsetDescriptor
(
BoxedCApiFunction
::
getname
,
NULL
,
NULL
));
capifunc_cls
->
giveAttr
(
capifunc_cls
->
giveAttr
(
...
...
src/runtime/descr.cpp
View file @
a9813b80
...
@@ -347,6 +347,9 @@ Box* BoxedMethodDescriptor::callInternal(BoxedFunctionBase* f, CallRewriteArgs*
...
@@ -347,6 +347,9 @@ Box* BoxedMethodDescriptor::callInternal(BoxedFunctionBase* f, CallRewriteArgs*
RELEASE_ASSERT
(
0
,
"0x%x"
,
call_flags
);
RELEASE_ASSERT
(
0
,
"0x%x"
,
call_flags
);
}
}
if
(
!
rtn
)
throwCAPIException
();
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
checkAndThrowCAPIException
);
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
checkAndThrowCAPIException
);
rewrite_args
->
out_rtn
=
r_rtn
;
rewrite_args
->
out_rtn
=
r_rtn
;
rewrite_args
->
out_success
=
true
;
rewrite_args
->
out_success
=
true
;
...
...
src/runtime/ics.h
View file @
a9813b80
...
@@ -68,7 +68,7 @@ protected:
...
@@ -68,7 +68,7 @@ protected:
class
CallattrIC
:
public
RuntimeIC
{
class
CallattrIC
:
public
RuntimeIC
{
public:
public:
CallattrIC
()
:
RuntimeIC
((
void
*
)
callattr
,
1
,
16
0
)
{}
CallattrIC
()
:
RuntimeIC
((
void
*
)
callattr
,
1
,
32
0
)
{}
Box
*
call
(
Box
*
obj
,
BoxedString
*
attr
,
CallattrFlags
flags
,
ArgPassSpec
spec
,
Box
*
arg0
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
call
(
Box
*
obj
,
BoxedString
*
attr
,
CallattrFlags
flags
,
ArgPassSpec
spec
,
Box
*
arg0
,
Box
*
arg1
,
Box
*
arg2
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
...
...
src/runtime/iterators.cpp
View file @
a9813b80
...
@@ -41,24 +41,13 @@ public:
...
@@ -41,24 +41,13 @@ public:
void
next
()
override
{
void
next
()
override
{
STAT_TIMER
(
t0
,
"us_timer_iteratorgeneric_next"
,
0
);
STAT_TIMER
(
t0
,
"us_timer_iteratorgeneric_next"
,
0
);
assert
(
iterator
);
Box
*
next
=
PyIter_Next
(
iterator
);
Box
*
hasnext
=
iterator
->
hasnextOrNullIC
();
if
(
next
)
if
(
hasnext
)
{
value
=
next
;
if
(
hasnext
->
nonzeroIC
())
{
else
{
value
=
iterator
->
nextIC
();
checkAndThrowCAPIException
();
}
else
{
*
this
=
*
end
();
*
this
=
*
end
();
}
}
}
else
{
try
{
value
=
iterator
->
nextIC
();
}
catch
(
ExcInfo
e
)
{
if
(
e
.
matches
(
StopIteration
))
*
this
=
*
end
();
else
throw
e
;
}
}
}
}
Box
*
getValue
()
override
{
return
value
;
}
Box
*
getValue
()
override
{
return
value
;
}
...
...
src/runtime/iterobject.cpp
View file @
a9813b80
...
@@ -132,20 +132,14 @@ bool iterwrapperHasnextUnboxed(Box* s) {
...
@@ -132,20 +132,14 @@ bool iterwrapperHasnextUnboxed(Box* s) {
RELEASE_ASSERT
(
s
->
cls
==
iterwrapper_cls
,
""
);
RELEASE_ASSERT
(
s
->
cls
==
iterwrapper_cls
,
""
);
BoxedIterWrapper
*
self
=
static_cast
<
BoxedIterWrapper
*>
(
s
);
BoxedIterWrapper
*
self
=
static_cast
<
BoxedIterWrapper
*>
(
s
);
static
BoxedString
*
next_str
=
static_cast
<
BoxedString
*>
(
PyString_InternFromString
(
"next"
));
Box
*
next
=
PyIter_Next
(
self
->
iter
);
Box
*
next
;
try
{
next
=
callattr
(
self
->
iter
,
next_str
,
CallattrFlags
({.
cls_only
=
true
,
.
null_on_nonexistent
=
false
}),
ArgPassSpec
(
0
),
NULL
,
NULL
,
NULL
,
NULL
,
NULL
);
}
catch
(
ExcInfo
e
)
{
if
(
e
.
matches
(
StopIteration
))
{
self
->
next
=
NULL
;
return
false
;
}
throw
e
;
}
self
->
next
=
next
;
self
->
next
=
next
;
return
true
;
if
(
!
next
)
{
if
(
PyErr_Occurred
()
&&
!
PyErr_ExceptionMatches
(
PyExc_StopIteration
))
throwCAPIException
();
PyErr_Clear
();
}
return
next
!=
NULL
;
}
}
Box
*
iterwrapperHasnext
(
Box
*
s
)
{
Box
*
iterwrapperHasnext
(
Box
*
s
)
{
...
...
src/runtime/list.cpp
View file @
a9813b80
...
@@ -101,7 +101,7 @@ extern "C" Box* listPop(BoxedList* self, Box* idx) {
...
@@ -101,7 +101,7 @@ extern "C" Box* listPop(BoxedList* self, Box* idx) {
int64_t
n
=
PyInt_AsSsize_t
(
idx
);
int64_t
n
=
PyInt_AsSsize_t
(
idx
);
if
(
n
==
-
1
&&
PyErr_Occurred
())
if
(
n
==
-
1
&&
PyErr_Occurred
())
raiseExcHelper
(
TypeError
,
"an integer is required"
);
throwCAPIException
(
);
if
(
n
<
0
)
if
(
n
<
0
)
n
=
self
->
size
+
n
;
n
=
self
->
size
+
n
;
...
...
src/runtime/objmodel.cpp
View file @
a9813b80
...
@@ -2723,11 +2723,22 @@ extern "C" Box* callattrInternal(Box* obj, BoxedString* attr, LookupScope scope,
...
@@ -2723,11 +2723,22 @@ extern "C" Box* callattrInternal(Box* obj, BoxedString* attr, LookupScope scope,
extern
"C"
Box
*
callattr
(
Box
*
obj
,
BoxedString
*
attr
,
CallattrFlags
flags
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
extern
"C"
Box
*
callattr
(
Box
*
obj
,
BoxedString
*
attr
,
CallattrFlags
flags
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
Box
*
arg3
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
STAT_TIMER
(
t0
,
"us_timer_slowpath_callattr"
,
10
);
STAT_TIMER
(
t0
,
"us_timer_slowpath_callattr"
,
10
);
#if 0
#if 0 && STAT_TIMERS
static uint64_t* st_id = Stats::getStatCounter("us_timer_slowpath_callattr_patchable");
static uint64_t* st_id = Stats::getStatCounter("us_timer_slowpath_callattr_patchable");
static uint64_t* st_id_nopatch = Stats::getStatCounter("us_timer_slowpath_callattr_nopatch");
static uint64_t* st_id_nopatch = Stats::getStatCounter("us_timer_slowpath_callattr_nopatch");
bool havepatch = (bool)getICInfo(__builtin_extract_return_addr(__builtin_return_address(0)));
static uint64_t* st_id_megamorphic = Stats::getStatCounter("us_timer_slowpath_callattr_megamorphic");
ScopedStatTimer st(havepatch ? st_id : st_id_nopatch, 10);
ICInfo* icinfo = getICInfo(__builtin_extract_return_addr(__builtin_return_address(0)));
uint64_t* counter;
if (!icinfo)
counter = st_id_nopatch;
else if (icinfo->isMegamorphic())
counter = st_id_megamorphic;
else {
//counter = Stats::getStatCounter("us_timer_slowpath_callattr_patchable_" + std::string(obj->cls->tp_name));
counter = Stats::getStatCounter("us_timer_slowpath_callattr_patchable_" + std::string(attr->s()));
}
ScopedStatTimer st(counter, 10);
#endif
#endif
ASSERT
(
gc
::
isValidGCObject
(
obj
),
"%p"
,
obj
);
ASSERT
(
gc
::
isValidGCObject
(
obj
),
"%p"
,
obj
);
...
@@ -3504,6 +3515,7 @@ Box* callCLFunc(CLFunction* f, CallRewriteArgs* rewrite_args, int num_output_arg
...
@@ -3504,6 +3515,7 @@ Box* callCLFunc(CLFunction* f, CallRewriteArgs* rewrite_args, int num_output_arg
ASSERT
(
chosen_cf
->
spec
->
rtn_type
->
isFitBy
(
r
->
cls
),
"%s (%p) was supposed to return %s, but gave a %s"
,
ASSERT
(
chosen_cf
->
spec
->
rtn_type
->
isFitBy
(
r
->
cls
),
"%s (%p) was supposed to return %s, but gave a %s"
,
g
.
func_addr_registry
.
getFuncNameAtAddress
(
chosen_cf
->
code
,
true
,
NULL
).
c_str
(),
chosen_cf
->
code
,
g
.
func_addr_registry
.
getFuncNameAtAddress
(
chosen_cf
->
code
,
true
,
NULL
).
c_str
(),
chosen_cf
->
code
,
chosen_cf
->
spec
->
rtn_type
->
debugName
().
c_str
(),
r
->
cls
->
tp_name
);
chosen_cf
->
spec
->
rtn_type
->
debugName
().
c_str
(),
r
->
cls
->
tp_name
);
assert
(
!
PyErr_Occurred
());
return
r
;
return
r
;
}
}
...
@@ -3514,13 +3526,13 @@ Box* runtimeCallInternal(Box* obj, CallRewriteArgs* rewrite_args, ArgPassSpec ar
...
@@ -3514,13 +3526,13 @@ Box* runtimeCallInternal(Box* obj, CallRewriteArgs* rewrite_args, ArgPassSpec ar
int
npassed_args
=
argspec
.
totalPassed
();
int
npassed_args
=
argspec
.
totalPassed
();
if
(
obj
->
cls
!=
function_cls
&&
obj
->
cls
!=
builtin_function_or_method_cls
&&
obj
->
cls
!=
instancemethod_cls
)
{
if
(
obj
->
cls
!=
function_cls
&&
obj
->
cls
!=
builtin_function_or_method_cls
&&
obj
->
cls
!=
instancemethod_cls
)
{
STAT_TIMER
(
t0
,
"us_timer_slowpath_runtimecall_nonfunction"
,
20
);
// TODO: maybe eventually runtimeCallInternal should just be the default tpp_call?
// TODO: maybe eventually runtimeCallInternal should just be the default tpp_call?
if
(
obj
->
cls
->
tpp_call
)
{
if
(
obj
->
cls
->
tpp_call
)
{
return
obj
->
cls
->
tpp_call
(
obj
,
rewrite_args
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_names
);
return
obj
->
cls
->
tpp_call
(
obj
,
rewrite_args
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_names
);
}
}
STAT_TIMER
(
t0
,
"us_timer_slowpath_runtimecall_nonfunction"
,
20
);
#if 0
#if 0
std::string per_name_stat_name = "zzz_runtimecall_nonfunction_" + std::string(obj->cls->tp_name);
std::string per_name_stat_name = "zzz_runtimecall_nonfunction_" + std::string(obj->cls->tp_name);
uint64_t* counter = Stats::getStatCounter(per_name_stat_name);
uint64_t* counter = Stats::getStatCounter(per_name_stat_name);
...
...
src/runtime/stacktrace.cpp
View file @
a9813b80
...
@@ -47,6 +47,7 @@ void showBacktrace() {
...
@@ -47,6 +47,7 @@ void showBacktrace() {
}
}
void
raiseExc
(
Box
*
exc_obj
)
{
void
raiseExc
(
Box
*
exc_obj
)
{
assert
(
!
PyErr_Occurred
());
throw
ExcInfo
(
exc_obj
->
cls
,
exc_obj
,
None
);
throw
ExcInfo
(
exc_obj
->
cls
,
exc_obj
,
None
);
}
}
...
@@ -56,6 +57,7 @@ void raiseSyntaxError(const char* msg, int lineno, int col_offset, llvm::StringR
...
@@ -56,6 +57,7 @@ void raiseSyntaxError(const char* msg, int lineno, int col_offset, llvm::StringR
Box
*
exc
=
runtimeCall
(
SyntaxError
,
ArgPassSpec
(
1
),
boxString
(
msg
),
NULL
,
NULL
,
NULL
,
NULL
);
Box
*
exc
=
runtimeCall
(
SyntaxError
,
ArgPassSpec
(
1
),
boxString
(
msg
),
NULL
,
NULL
,
NULL
,
NULL
);
auto
tb
=
new
BoxedTraceback
(
LineInfo
(
lineno
,
col_offset
,
file
,
func
),
None
);
auto
tb
=
new
BoxedTraceback
(
LineInfo
(
lineno
,
col_offset
,
file
,
func
),
None
);
assert
(
!
PyErr_Occurred
());
throw
ExcInfo
(
exc
->
cls
,
exc
,
tb
);
throw
ExcInfo
(
exc
->
cls
,
exc
,
tb
);
}
}
...
@@ -175,6 +177,7 @@ extern "C" void raise0() {
...
@@ -175,6 +177,7 @@ extern "C" void raise0() {
raiseExcHelper
(
TypeError
,
"exceptions must be old-style classes or derived from BaseException, not NoneType"
);
raiseExcHelper
(
TypeError
,
"exceptions must be old-style classes or derived from BaseException, not NoneType"
);
exc_info
->
reraise
=
true
;
exc_info
->
reraise
=
true
;
assert
(
!
PyErr_Occurred
());
throw
*
exc_info
;
throw
*
exc_info
;
}
}
...
@@ -256,6 +259,7 @@ extern "C" void raise3(Box* arg0, Box* arg1, Box* arg2) {
...
@@ -256,6 +259,7 @@ extern "C" void raise3(Box* arg0, Box* arg1, Box* arg2) {
auto
exc_info
=
excInfoForRaise
(
arg0
,
arg1
,
arg2
);
auto
exc_info
=
excInfoForRaise
(
arg0
,
arg1
,
arg2
);
exc_info
.
reraise
=
reraise
;
exc_info
.
reraise
=
reraise
;
assert
(
!
PyErr_Occurred
());
throw
exc_info
;
throw
exc_info
;
}
}
...
...
src/runtime/types.cpp
View file @
a9813b80
...
@@ -238,9 +238,38 @@ Box* BoxedClass::callHasnextIC(Box* obj, bool null_on_nonexistent) {
...
@@ -238,9 +238,38 @@ Box* BoxedClass::callHasnextIC(Box* obj, bool null_on_nonexistent) {
ArgPassSpec
(
0
),
nullptr
,
nullptr
,
nullptr
,
nullptr
,
nullptr
);
ArgPassSpec
(
0
),
nullptr
,
nullptr
,
nullptr
,
nullptr
,
nullptr
);
}
}
extern
"C"
PyObject
*
PyIter_Next
(
PyObject
*
iter
)
noexcept
{
if
(
iter
->
cls
->
tp_iternext
!=
slot_tp_iternext
)
{
PyObject
*
result
;
result
=
(
*
iter
->
cls
->
tp_iternext
)(
iter
);
if
(
result
==
NULL
&&
PyErr_Occurred
()
&&
PyErr_ExceptionMatches
(
PyExc_StopIteration
))
PyErr_Clear
();
return
result
;
}
try
{
Box
*
hasnext
=
iter
->
hasnextOrNullIC
();
if
(
hasnext
)
{
if
(
hasnext
->
nonzeroIC
())
return
iter
->
cls
->
callNextIC
(
iter
);
else
return
NULL
;
}
else
{
return
iter
->
cls
->
callNextIC
(
iter
);
}
}
catch
(
ExcInfo
e
)
{
if
(
!
e
.
matches
(
StopIteration
))
setCAPIException
(
e
);
return
NULL
;
}
}
Box
*
BoxedClass
::
callNextIC
(
Box
*
obj
)
{
Box
*
BoxedClass
::
callNextIC
(
Box
*
obj
)
{
assert
(
obj
->
cls
==
this
);
assert
(
obj
->
cls
==
this
);
// This would work, but it would have been better to just call tp_iternext
assert
(
this
->
tp_iternext
==
slot_tp_iternext
);
auto
ic
=
next_ic
.
get
();
auto
ic
=
next_ic
.
get
();
if
(
!
ic
)
{
if
(
!
ic
)
{
ic
=
new
CallattrIC
();
ic
=
new
CallattrIC
();
...
@@ -303,11 +332,6 @@ Box* Box::hasnextOrNullIC() {
...
@@ -303,11 +332,6 @@ Box* Box::hasnextOrNullIC() {
return
this
->
cls
->
callHasnextIC
(
this
,
true
);
return
this
->
cls
->
callHasnextIC
(
this
,
true
);
}
}
Box
*
Box
::
nextIC
()
{
return
this
->
cls
->
callNextIC
(
this
);
}
std
::
string
builtinStr
(
"__builtin__"
);
std
::
string
builtinStr
(
"__builtin__"
);
extern
"C"
BoxedFunctionBase
::
BoxedFunctionBase
(
CLFunction
*
f
)
extern
"C"
BoxedFunctionBase
::
BoxedFunctionBase
(
CLFunction
*
f
)
...
@@ -580,6 +604,42 @@ static void assertInitNone(Box* obj) {
...
@@ -580,6 +604,42 @@ static void assertInitNone(Box* obj) {
}
}
}
}
static
PyObject
*
cpython_type_call
(
PyTypeObject
*
type
,
PyObject
*
args
,
PyObject
*
kwds
)
noexcept
{
PyObject
*
obj
;
if
(
type
->
tp_new
==
NULL
)
{
PyErr_Format
(
PyExc_TypeError
,
"cannot create '%.100s' instances"
,
type
->
tp_name
);
return
NULL
;
}
obj
=
type
->
tp_new
(
type
,
args
,
kwds
);
if
(
obj
!=
NULL
)
{
/* Ugly exception: when the call was type(something),
* don't call tp_init on the result. */
if
(
type
==
&
PyType_Type
&&
PyTuple_Check
(
args
)
&&
PyTuple_GET_SIZE
(
args
)
==
1
&&
(
kwds
==
NULL
||
(
PyDict_Check
(
kwds
)
&&
PyDict_Size
(
kwds
)
==
0
)))
return
obj
;
/* If the returned object is not an instance of type,
* it won't be initialized. */
if
(
!
PyType_IsSubtype
(
obj
->
cls
,
type
))
return
obj
;
type
=
obj
->
cls
;
if
(
PyType_HasFeature
(
type
,
Py_TPFLAGS_HAVE_CLASS
)
&&
type
->
tp_init
!=
NULL
&&
type
->
tp_init
(
obj
,
args
,
kwds
)
<
0
)
{
Py_DECREF
(
obj
);
obj
=
NULL
;
}
}
return
obj
;
}
static
PyObject
*
cpythonTypeCall
(
BoxedClass
*
type
,
PyObject
*
args
,
PyObject
*
kwds
)
{
Box
*
r
=
cpython_type_call
(
type
,
args
,
kwds
);
if
(
!
r
)
throwCAPIException
();
return
r
;
}
static
Box
*
typeCallInner
(
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
static
Box
*
typeCallInner
(
CallRewriteArgs
*
rewrite_args
,
ArgPassSpec
argspec
,
Box
*
arg1
,
Box
*
arg2
,
Box
*
arg3
,
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
Box
**
args
,
const
std
::
vector
<
BoxedString
*>*
keyword_names
)
{
int
npassed_args
=
argspec
.
totalPassed
();
int
npassed_args
=
argspec
.
totalPassed
();
...
@@ -594,6 +654,29 @@ static Box* typeCallInner(CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Bo
...
@@ -594,6 +654,29 @@ static Box* typeCallInner(CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Bo
BoxedClass
*
cls
=
static_cast
<
BoxedClass
*>
(
_cls
);
BoxedClass
*
cls
=
static_cast
<
BoxedClass
*>
(
_cls
);
if
(
cls
->
tp_new
!=
object_cls
->
tp_new
&&
cls
->
tp_new
!=
slot_tp_new
)
{
// Looks like we're calling an extension class and we're not going to be able to
// separately rewrite the new + init calls. But we can rewrite the fact that we
// should just call the cpython version, which will end up working pretty well.
ParamReceiveSpec
paramspec
(
1
,
false
,
true
,
true
);
bool
rewrite_success
=
false
;
Box
*
oarg1
,
*
oarg2
,
*
oarg3
,
**
oargs
=
NULL
;
rearrangeArguments
(
paramspec
,
NULL
,
""
,
NULL
,
rewrite_args
,
rewrite_success
,
argspec
,
arg1
,
arg2
,
arg3
,
args
,
keyword_names
,
oarg1
,
oarg2
,
oarg3
,
oargs
);
assert
(
oarg1
==
cls
);
if
(
!
rewrite_success
)
rewrite_args
=
NULL
;
if
(
rewrite_args
)
{
rewrite_args
->
out_rtn
=
rewrite_args
->
rewriter
->
call
(
true
,
(
void
*
)
cpythonTypeCall
,
rewrite_args
->
arg1
,
rewrite_args
->
arg2
,
rewrite_args
->
arg3
);
rewrite_args
->
out_success
=
true
;
}
return
cpythonTypeCall
(
cls
,
oarg2
,
oarg3
);
}
RewriterVar
*
r_ccls
=
NULL
;
RewriterVar
*
r_ccls
=
NULL
;
RewriterVar
*
r_new
=
NULL
;
RewriterVar
*
r_new
=
NULL
;
RewriterVar
*
r_init
=
NULL
;
RewriterVar
*
r_init
=
NULL
;
...
@@ -769,6 +852,9 @@ static Box* typeCallInner(CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Bo
...
@@ -769,6 +852,9 @@ static Box* typeCallInner(CallRewriteArgs* rewrite_args, ArgPassSpec argspec, Bo
"We should only have allowed the rewrite to continue if we were guaranteed that made "
"We should only have allowed the rewrite to continue if we were guaranteed that made "
"would have class cls!"
);
"would have class cls!"
);
}
else
{
}
else
{
if
(
cls
->
tp_new
==
object_cls
->
tp_new
&&
cls
->
tp_init
!=
object_cls
->
tp_init
)
made
=
objectNewNoArgs
(
cls
);
else
made
=
runtimeCallInternal
(
new_attr
,
NULL
,
new_argspec
,
cls
,
arg2
,
arg3
,
args
,
keyword_names
);
made
=
runtimeCallInternal
(
new_attr
,
NULL
,
new_argspec
,
cls
,
arg2
,
arg3
,
args
,
keyword_names
);
}
}
...
...
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