Commit 2fd43416 authored by gsathya's avatar gsathya Committed by Commit bot

[promises] Remove runtime call from PromiseReject

Also moves some functions to prologue.js

BUG=v8:5343

Review-Url: https://codereview.chromium.org/2630593004
Cr-Commit-Position: refs/heads/master@{#42417}
parent 67154399
...@@ -7307,9 +7307,12 @@ Maybe<bool> Promise::Resolver::Reject(Local<Context> context, ...@@ -7307,9 +7307,12 @@ Maybe<bool> Promise::Resolver::Reject(Local<Context> context,
Local<Value> value) { Local<Value> value) {
PREPARE_FOR_EXECUTION_PRIMITIVE(context, Promise_Resolver, Resolve, bool); PREPARE_FOR_EXECUTION_PRIMITIVE(context, Promise_Resolver, Resolve, bool);
auto self = Utils::OpenHandle(this); auto self = Utils::OpenHandle(this);
i::Handle<i::Object> argv[] = {self, Utils::OpenHandle(*value)};
// We pass true to trigger the debugger's on exception handler.
i::Handle<i::Object> argv[] = {self, Utils::OpenHandle(*value),
isolate->factory()->ToBoolean(true)};
has_pending_exception = has_pending_exception =
i::Execution::Call(isolate, isolate->promise_reject(), i::Execution::Call(isolate, isolate->promise_internal_reject(),
isolate->factory()->undefined_value(), arraysize(argv), isolate->factory()->undefined_value(), arraysize(argv),
argv) argv)
.is_null(); .is_null();
......
...@@ -1971,6 +1971,14 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object, ...@@ -1971,6 +1971,14 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
promise_handle->set_is_exception_caught(true); promise_handle->set_is_exception_caught(true);
} }
{ // Internal: InternalPromiseReject
Handle<JSFunction> function =
SimpleCreateFunction(isolate, factory->empty_string(),
Builtins::kInternalPromiseReject, 3, true);
InstallWithIntrinsicDefaultProto(isolate, function,
Context::PROMISE_INTERNAL_REJECT_INDEX);
}
{ {
Handle<Code> code = Handle<Code> code =
handle(isolate->builtins()->builtin(Builtins::kPromiseResolveClosure), handle(isolate->builtins()->builtin(Builtins::kPromiseResolveClosure),
......
...@@ -748,8 +748,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, ...@@ -748,8 +748,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
Bind(&reject); Bind(&reject);
// Don't cause a debug event as this case is forwarding a rejection // Don't cause a debug event as this case is forwarding a rejection
CallRuntime(Runtime::kPromiseReject, context, promise, thenable_value, InternalPromiseReject(context, promise, thenable_value, false);
FalseConstant());
PromiseSetHasHandler(result); PromiseSetHasHandler(result);
Goto(&out); Goto(&out);
} }
...@@ -836,8 +835,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context, ...@@ -836,8 +835,7 @@ void PromiseBuiltinsAssembler::InternalResolvePromise(Node* context,
// 9.a Return RejectPromise(promise, then.[[Value]]). // 9.a Return RejectPromise(promise, then.[[Value]]).
Bind(&if_rejectpromise); Bind(&if_rejectpromise);
{ {
CallRuntime(Runtime::kPromiseReject, context, promise, var_reason.value(), InternalPromiseReject(context, promise, var_reason.value(), true);
TrueConstant());
Goto(&out); Goto(&out);
} }
...@@ -943,6 +941,52 @@ void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed( ...@@ -943,6 +941,52 @@ void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
Bind(&has_access); Bind(&has_access);
} }
void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context,
Node* promise, Node* value,
Node* debug_event) {
Label out(this);
GotoUnless(IsDebugActive(), &out);
GotoUnless(WordEqual(TrueConstant(), debug_event), &out);
CallRuntime(Runtime::kDebugPromiseReject, context, promise, value);
Goto(&out);
Bind(&out);
InternalPromiseReject(context, promise, value, false);
}
// This duplicates a lot of logic from PromiseRejectEvent in
// runtime-promise.cc
void PromiseBuiltinsAssembler::InternalPromiseReject(Node* context,
Node* promise, Node* value,
bool debug_event) {
Label fulfill(this), report_unhandledpromise(this), run_promise_hook(this);
if (debug_event) {
GotoUnless(IsDebugActive(), &run_promise_hook);
CallRuntime(Runtime::kDebugPromiseReject, context, promise, value);
Goto(&run_promise_hook);
} else {
Goto(&run_promise_hook);
}
Bind(&run_promise_hook);
{
GotoUnless(IsPromiseHookEnabled(), &report_unhandledpromise);
CallRuntime(Runtime::kPromiseHookResolve, context, promise);
Goto(&report_unhandledpromise);
}
Bind(&report_unhandledpromise);
{
GotoIf(PromiseHasHandler(promise), &fulfill);
CallRuntime(Runtime::kReportPromiseReject, context, promise, value);
Goto(&fulfill);
}
Bind(&fulfill);
PromiseFulfill(context, promise, value, v8::Promise::kRejected);
}
// ES#sec-promise-reject-functions // ES#sec-promise-reject-functions
// Promise Reject Functions // Promise Reject Functions
TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) { TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) {
...@@ -970,7 +1014,7 @@ TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) { ...@@ -970,7 +1014,7 @@ TF_BUILTIN(PromiseRejectClosure, PromiseBuiltinsAssembler) {
Node* const debug_event = LoadContextElement( Node* const debug_event = LoadContextElement(
context, IntPtrConstant(PromiseUtils::kDebugEventSlot)); context, IntPtrConstant(PromiseUtils::kDebugEventSlot));
CallRuntime(Runtime::kPromiseReject, context, promise, value, debug_event); InternalPromiseReject(context, promise, value, debug_event);
Return(UndefinedConstant()); Return(UndefinedConstant());
Bind(&out); Bind(&out);
...@@ -1207,8 +1251,7 @@ TF_BUILTIN(PromiseHandleReject, PromiseBuiltinsAssembler) { ...@@ -1207,8 +1251,7 @@ TF_BUILTIN(PromiseHandleReject, PromiseBuiltinsAssembler) {
Bind(&if_internalhandler); Bind(&if_internalhandler);
{ {
CallRuntime(Runtime::kPromiseReject, context, promise, exception, InternalPromiseReject(context, promise, exception, false);
FalseConstant());
Return(UndefinedConstant()); Return(UndefinedConstant());
} }
...@@ -1504,5 +1547,15 @@ TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler) { ...@@ -1504,5 +1547,15 @@ TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler) {
} }
} }
TF_BUILTIN(InternalPromiseReject, PromiseBuiltinsAssembler) {
Node* const promise = Parameter(1);
Node* const reason = Parameter(2);
Node* const debug_event = Parameter(3);
Node* const context = Parameter(6);
InternalPromiseReject(context, promise, reason, debug_event);
Return(UndefinedConstant());
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -34,6 +34,23 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -34,6 +34,23 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* resolve, Node* reject, Node* resolve, Node* reject,
Node* context); Node* context);
std::pair<Node*, Node*> CreatePromiseResolvingFunctions(
Node* promise, Node* native_context, Node* promise_context);
Node* PromiseHasHandler(Node* promise);
Node* CreatePromiseResolvingFunctionsContext(Node* promise, Node* debug_event,
Node* native_context);
Node* CreatePromiseGetCapabilitiesExecutorContext(Node* native_context,
Node* promise_capability);
Node* NewPromiseCapability(Node* context, Node* constructor,
Node* debug_event = nullptr);
protected:
void PromiseInit(Node* promise);
Node* ThrowIfNotJSReceiver(Node* context, Node* value, Node* ThrowIfNotJSReceiver(Node* context, Node* value,
MessageTemplate::Template msg_template, MessageTemplate::Template msg_template,
const char* method_name = nullptr); const char* method_name = nullptr);
...@@ -41,8 +58,6 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -41,8 +58,6 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Node* SpeciesConstructor(Node* context, Node* object, Node* SpeciesConstructor(Node* context, Node* object,
Node* default_constructor); Node* default_constructor);
Node* PromiseHasHandler(Node* promise);
void PromiseSetHasHandler(Node* promise); void PromiseSetHasHandler(Node* promise);
void AppendPromiseCallback(int offset, compiler::Node* promise, void AppendPromiseCallback(int offset, compiler::Node* promise,
...@@ -66,27 +81,17 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler { ...@@ -66,27 +81,17 @@ class PromiseBuiltinsAssembler : public CodeStubAssembler {
Label* if_isunmodified, Label* if_ismodified); Label* if_isunmodified, Label* if_ismodified);
Node* CreatePromiseContext(Node* native_context, int slots); Node* CreatePromiseContext(Node* native_context, int slots);
Node* CreatePromiseResolvingFunctionsContext(Node* promise, Node* debug_event,
Node* native_context);
std::pair<Node*, Node*> CreatePromiseResolvingFunctions(
Node* promise, Node* native_context, Node* promise_context);
Node* CreatePromiseGetCapabilitiesExecutorContext(Node* native_context,
Node* promise_capability);
void PromiseFulfill(Node* context, Node* promise, Node* result, void PromiseFulfill(Node* context, Node* promise, Node* result,
v8::Promise::PromiseState status); v8::Promise::PromiseState status);
Node* NewPromiseCapability(Node* context, Node* constructor,
Node* debug_event = nullptr);
void BranchIfAccessCheckFailed(Node* context, Node* native_context, void BranchIfAccessCheckFailed(Node* context, Node* native_context,
Node* promise_constructor, Node* executor, Node* promise_constructor, Node* executor,
Label* if_noaccess); Label* if_noaccess);
protected: void InternalPromiseReject(Node* context, Node* promise, Node* value,
void PromiseInit(Node* promise); bool debug_event);
void InternalPromiseReject(Node* context, Node* promise, Node* value,
Node* debug_event);
private: private:
Node* AllocateJSPromise(Node* context); Node* AllocateJSPromise(Node* context);
......
...@@ -639,6 +639,7 @@ namespace internal { ...@@ -639,6 +639,7 @@ namespace internal {
TFJ(PromiseHandle, 5) \ TFJ(PromiseHandle, 5) \
TFJ(PromiseResolve, 1) \ TFJ(PromiseResolve, 1) \
TFJ(PromiseReject, 1) \ TFJ(PromiseReject, 1) \
TFJ(InternalPromiseReject, 3) \
\ \
/* Proxy */ \ /* Proxy */ \
CPP(ProxyConstructor) \ CPP(ProxyConstructor) \
......
...@@ -64,6 +64,7 @@ enum ContextLookupFlags { ...@@ -64,6 +64,7 @@ enum ContextLookupFlags {
V(NEW_PROMISE_CAPABILITY_INDEX, JSFunction, new_promise_capability) \ V(NEW_PROMISE_CAPABILITY_INDEX, JSFunction, new_promise_capability) \
V(PROMISE_INTERNAL_CONSTRUCTOR_INDEX, JSFunction, \ V(PROMISE_INTERNAL_CONSTRUCTOR_INDEX, JSFunction, \
promise_internal_constructor) \ promise_internal_constructor) \
V(PROMISE_INTERNAL_REJECT_INDEX, JSFunction, promise_internal_reject) \
V(IS_PROMISE_INDEX, JSFunction, is_promise) \ V(IS_PROMISE_INDEX, JSFunction, is_promise) \
V(PERFORM_PROMISE_THEN_INDEX, JSFunction, perform_promise_then) \ V(PERFORM_PROMISE_THEN_INDEX, JSFunction, perform_promise_then) \
V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \ V(PROMISE_RESOLVE_INDEX, JSFunction, promise_resolve) \
...@@ -103,7 +104,6 @@ enum ContextLookupFlags { ...@@ -103,7 +104,6 @@ enum ContextLookupFlags {
V(OBJECT_TO_STRING, JSFunction, object_to_string) \ V(OBJECT_TO_STRING, JSFunction, object_to_string) \
V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \ V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \
V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \ V(PROMISE_FUNCTION_INDEX, JSFunction, promise_function) \
V(PROMISE_REJECT_INDEX, JSFunction, promise_reject) \
V(PROMISE_ID_RESOLVE_HANDLER_INDEX, JSFunction, promise_id_resolve_handler) \ V(PROMISE_ID_RESOLVE_HANDLER_INDEX, JSFunction, promise_id_resolve_handler) \
V(PROMISE_ID_REJECT_HANDLER_INDEX, JSFunction, promise_id_reject_handler) \ V(PROMISE_ID_REJECT_HANDLER_INDEX, JSFunction, promise_id_reject_handler) \
V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \ V(RANGE_ERROR_FUNCTION_INDEX, JSFunction, range_error_function) \
......
...@@ -108,7 +108,7 @@ function AsyncFunctionAwaitCaught(generator, awaited, outerPromise) { ...@@ -108,7 +108,7 @@ function AsyncFunctionAwaitCaught(generator, awaited, outerPromise) {
// How the parser rejects promises from async/await desugaring // How the parser rejects promises from async/await desugaring
function RejectPromiseNoDebugEvent(promise, reason) { function RejectPromiseNoDebugEvent(promise, reason) {
return %PromiseReject(promise, reason, false); return %promise_internal_reject(promise, reason, false);
} }
function AsyncFunctionPromiseCreate() { function AsyncFunctionPromiseCreate() {
......
...@@ -266,7 +266,7 @@ utils.PostDebug = PostDebug; ...@@ -266,7 +266,7 @@ utils.PostDebug = PostDebug;
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
%OptimizeObjectForAddingMultipleProperties(extrasUtils, 5); %OptimizeObjectForAddingMultipleProperties(extrasUtils, 7);
extrasUtils.logStackTrace = function logStackTrace() { extrasUtils.logStackTrace = function logStackTrace() {
%DebugTrace(); %DebugTrace();
...@@ -307,6 +307,15 @@ extrasUtils.uncurryThis = function uncurryThis(func) { ...@@ -307,6 +307,15 @@ extrasUtils.uncurryThis = function uncurryThis(func) {
}; };
}; };
// We pass true to trigger the debugger's on exception handler.
extrasUtils.rejectPromise = function rejectPromise(promise, reason) {
%promise_internal_reject(promise, reason, true);
}
extrasUtils.markPromiseAsHandled = function markPromiseAsHandled(promise) {
%PromiseMarkAsHandled(promise);
};
%ToFastProperties(extrasUtils); %ToFastProperties(extrasUtils);
}) })
...@@ -29,13 +29,6 @@ SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true); ...@@ -29,13 +29,6 @@ SET_PRIVATE(PromiseIdRejectHandler, promiseForwardingHandlerSymbol, true);
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Define exported functions. // Define exported functions.
// For bootstrapper.
// Export to bindings
function DoRejectPromise(promise, reason) {
%PromiseReject(promise, reason, true);
}
// Combinators. // Combinators.
// ES#sec-promise.all // ES#sec-promise.all
...@@ -136,10 +129,6 @@ function PromiseRace(iterable) { ...@@ -136,10 +129,6 @@ function PromiseRace(iterable) {
return deferred.promise; return deferred.promise;
} }
function MarkPromiseAsHandled(promise) {
%PromiseMarkAsHandled(promise);
}
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Install exported functions. // Install exported functions.
...@@ -149,17 +138,8 @@ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [ ...@@ -149,17 +138,8 @@ utils.InstallFunctions(GlobalPromise, DONT_ENUM, [
]); ]);
%InstallToContext([ %InstallToContext([
"promise_reject", DoRejectPromise,
"promise_id_resolve_handler", PromiseIdResolveHandler, "promise_id_resolve_handler", PromiseIdResolveHandler,
"promise_id_reject_handler", PromiseIdRejectHandler "promise_id_reject_handler", PromiseIdRejectHandler
]); ]);
// This allows extras to create promises quickly without building extra
// resolve/reject closures, and allows them to later resolve and reject any
// promise without having to hold on to those closures forever.
utils.InstallFunctions(extrasUtils, 0, [
"rejectPromise", DoRejectPromise,
"markPromiseAsHandled", MarkPromiseAsHandled
]);
}) })
...@@ -1896,6 +1896,16 @@ RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionPromiseCreated) { ...@@ -1896,6 +1896,16 @@ RUNTIME_FUNCTION(Runtime_DebugAsyncFunctionPromiseCreated) {
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
} }
RUNTIME_FUNCTION(Runtime_DebugPromiseReject) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSPromise, rejected_promise, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
isolate->debug()->OnPromiseReject(rejected_promise, value);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DebugAsyncEventEnqueueRecurring) { RUNTIME_FUNCTION(Runtime_DebugAsyncEventEnqueueRecurring) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(2, args.length()); DCHECK_EQ(2, args.length());
......
...@@ -51,6 +51,16 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { ...@@ -51,6 +51,16 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) {
return isolate->heap()->undefined_value(); return isolate->heap()->undefined_value();
} }
RUNTIME_FUNCTION(Runtime_ReportPromiseReject) {
DCHECK_EQ(2, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
isolate->ReportPromiseReject(Handle<JSObject>::cast(promise), value,
v8::kPromiseRejectWithNoHandler);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) { RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) {
DCHECK_EQ(1, args.length()); DCHECK_EQ(1, args.length());
HandleScope scope(isolate); HandleScope scope(isolate);
...@@ -130,58 +140,8 @@ void EnqueuePromiseReactionJob(Isolate* isolate, Handle<JSPromise> promise, ...@@ -130,58 +140,8 @@ void EnqueuePromiseReactionJob(Isolate* isolate, Handle<JSPromise> promise,
isolate->EnqueueMicrotask(info); isolate->EnqueueMicrotask(info);
} }
void PromiseSet(Isolate* isolate, Handle<JSPromise> promise, int status,
Handle<Object> result) {
promise->set_status(status);
promise->set_result(*result);
promise->set_deferred_promise(isolate->heap()->undefined_value());
promise->set_deferred_on_resolve(isolate->heap()->undefined_value());
promise->set_deferred_on_reject(isolate->heap()->undefined_value());
promise->set_fulfill_reactions(isolate->heap()->undefined_value());
promise->set_reject_reactions(isolate->heap()->undefined_value());
}
void PromiseFulfill(Isolate* isolate, Handle<JSPromise> promise, int status,
Handle<Object> value) {
if (isolate->debug()->is_active()) {
isolate->debug()->OnAsyncTaskEvent(
status == v8::Promise::kFulfilled ? debug::kDebugEnqueuePromiseResolve
: debug::kDebugEnqueuePromiseReject,
isolate->debug()->NextAsyncTaskId(promise));
}
// Check if there are any callbacks.
if (!promise->deferred_promise()->IsUndefined(isolate)) {
Handle<Object> tasks((status == v8::Promise::kFulfilled)
? promise->fulfill_reactions()
: promise->reject_reactions(),
isolate);
Handle<PromiseReactionJobInfo> info =
isolate->factory()->NewPromiseReactionJobInfo(
value, tasks, handle(promise->deferred_promise(), isolate),
handle(promise->deferred_on_resolve(), isolate),
handle(promise->deferred_on_reject(), isolate),
isolate->native_context());
EnqueuePromiseReactionJob(isolate, promise, info, status);
}
PromiseSet(isolate, promise, status, value);
}
} // namespace } // namespace
RUNTIME_FUNCTION(Runtime_PromiseReject) {
DCHECK_EQ(3, args.length());
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSPromise, promise, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, reason, 1);
CONVERT_BOOLEAN_ARG_CHECKED(debug_event, 2);
PromiseRejectEvent(isolate, promise, promise, reason, debug_event);
PromiseFulfill(isolate, promise, v8::Promise::kRejected, reason);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_EnqueuePromiseReactionJob) { RUNTIME_FUNCTION(Runtime_EnqueuePromiseReactionJob) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(3, args.length()); DCHECK_EQ(3, args.length());
......
...@@ -199,6 +199,7 @@ namespace internal { ...@@ -199,6 +199,7 @@ namespace internal {
F(DebugRecordGenerator, 1, 1) \ F(DebugRecordGenerator, 1, 1) \
F(DebugPushPromise, 1, 1) \ F(DebugPushPromise, 1, 1) \
F(DebugPopPromise, 0, 1) \ F(DebugPopPromise, 0, 1) \
F(DebugPromiseReject, 2, 1) \
F(DebugNextAsyncTaskId, 1, 1) \ F(DebugNextAsyncTaskId, 1, 1) \
F(DebugAsyncEventEnqueueRecurring, 2, 1) \ F(DebugAsyncEventEnqueueRecurring, 2, 1) \
F(DebugAsyncFunctionPromiseCreated, 1, 1) \ F(DebugAsyncFunctionPromiseCreated, 1, 1) \
...@@ -304,7 +305,7 @@ namespace internal { ...@@ -304,7 +305,7 @@ namespace internal {
F(NewSyntaxError, 2, 1) \ F(NewSyntaxError, 2, 1) \
F(NewTypeError, 2, 1) \ F(NewTypeError, 2, 1) \
F(OrdinaryHasInstance, 2, 1) \ F(OrdinaryHasInstance, 2, 1) \
F(PromiseReject, 3, 1) \ F(ReportPromiseReject, 2, 1) \
F(PromiseHookInit, 2, 1) \ F(PromiseHookInit, 2, 1) \
F(PromiseHookResolve, 1, 1) \ F(PromiseHookResolve, 1, 1) \
F(PromiseHookBefore, 1, 1) \ F(PromiseHookBefore, 1, 1) \
......
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