Commit 2257f6b1 authored by Georg Neis's avatar Georg Neis Committed by V8 LUCI CQ

[compiler][test] Remove --block-concurrent-recompilation

- Remove flag --block-concurrent-recompilation and its implementation,
  including %UnblockConcurrentCompilation.
- Rewrite tests that used it in terms of the primitives introduced in
  my previous CL:
  https://chromium-review.googlesource.com/c/v8/v8/+/3071400/
- Remove "sync"/"no sync" arguments from %GetOptimizationStatus,
  assertOptimized, etc. These are now always "no sync": they don't
  do any magic.
- Remove "if %IsConcurrentRecompilationSupported then quit" from some
  tests in favor of --concurrent-recompilation in their Flags line.

Bug: v8:12041, v8:7790
Change-Id: I966aae4fec85e6f9e7aeed2ba2c12e9198a3991f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3077149Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#76298}
parent a016cce5
...@@ -173,7 +173,6 @@ void OptimizingCompileDispatcher::AwaitCompileTasks() { ...@@ -173,7 +173,6 @@ void OptimizingCompileDispatcher::AwaitCompileTasks() {
void OptimizingCompileDispatcher::FlushQueues( void OptimizingCompileDispatcher::FlushQueues(
BlockingBehavior blocking_behavior, bool restore_function_code) { BlockingBehavior blocking_behavior, bool restore_function_code) {
if (FLAG_block_concurrent_recompilation) Unblock();
FlushInputQueue(); FlushInputQueue();
if (blocking_behavior == BlockingBehavior::kBlock) { if (blocking_behavior == BlockingBehavior::kBlock) {
base::MutexGuard lock_guard(&ref_count_mutex_); base::MutexGuard lock_guard(&ref_count_mutex_);
...@@ -231,7 +230,7 @@ bool OptimizingCompileDispatcher::HasJobs() { ...@@ -231,7 +230,7 @@ bool OptimizingCompileDispatcher::HasJobs() {
// Note: This relies on {output_queue_} being mutated by a background thread // Note: This relies on {output_queue_} being mutated by a background thread
// only when {ref_count_} is not zero. Also, {ref_count_} is never incremented // only when {ref_count_} is not zero. Also, {ref_count_} is never incremented
// by a background thread. // by a background thread.
return ref_count_ != 0 || !output_queue_.empty() || blocked_jobs_ != 0; return ref_count_ != 0 || !output_queue_.empty();
} }
void OptimizingCompileDispatcher::QueueForOptimization( void OptimizingCompileDispatcher::QueueForOptimization(
...@@ -244,20 +243,8 @@ void OptimizingCompileDispatcher::QueueForOptimization( ...@@ -244,20 +243,8 @@ void OptimizingCompileDispatcher::QueueForOptimization(
input_queue_[InputQueueIndex(input_queue_length_)] = job; input_queue_[InputQueueIndex(input_queue_length_)] = job;
input_queue_length_++; input_queue_length_++;
} }
if (FLAG_block_concurrent_recompilation) { V8::GetCurrentPlatform()->CallOnWorkerThread(
blocked_jobs_++; std::make_unique<CompileTask>(isolate_, this));
} else {
V8::GetCurrentPlatform()->CallOnWorkerThread(
std::make_unique<CompileTask>(isolate_, this));
}
}
void OptimizingCompileDispatcher::Unblock() {
while (blocked_jobs_ > 0) {
V8::GetCurrentPlatform()->CallOnWorkerThread(
std::make_unique<CompileTask>(isolate_, this));
blocked_jobs_--;
}
} }
} // namespace internal } // namespace internal
......
...@@ -30,7 +30,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher { ...@@ -30,7 +30,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher {
input_queue_capacity_(FLAG_concurrent_recompilation_queue_length), input_queue_capacity_(FLAG_concurrent_recompilation_queue_length),
input_queue_length_(0), input_queue_length_(0),
input_queue_shift_(0), input_queue_shift_(0),
blocked_jobs_(0),
ref_count_(0), ref_count_(0),
recompilation_delay_(FLAG_concurrent_recompilation_delay) { recompilation_delay_(FLAG_concurrent_recompilation_delay) {
input_queue_ = NewArray<OptimizedCompilationJob*>(input_queue_capacity_); input_queue_ = NewArray<OptimizedCompilationJob*>(input_queue_capacity_);
...@@ -42,7 +41,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher { ...@@ -42,7 +41,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher {
void Flush(BlockingBehavior blocking_behavior); void Flush(BlockingBehavior blocking_behavior);
// Takes ownership of |job|. // Takes ownership of |job|.
void QueueForOptimization(OptimizedCompilationJob* job); void QueueForOptimization(OptimizedCompilationJob* job);
void Unblock();
void AwaitCompileTasks(); void AwaitCompileTasks();
void InstallOptimizedFunctions(); void InstallOptimizedFunctions();
...@@ -99,8 +97,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher { ...@@ -99,8 +97,6 @@ class V8_EXPORT_PRIVATE OptimizingCompileDispatcher {
// different threads. // different threads.
base::Mutex output_queue_mutex_; base::Mutex output_queue_mutex_;
int blocked_jobs_;
std::atomic<int> ref_count_; std::atomic<int> ref_count_;
base::Mutex ref_count_mutex_; base::Mutex ref_count_mutex_;
base::ConditionVariable ref_count_zero_; base::ConditionVariable ref_count_zero_;
......
...@@ -391,8 +391,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) { ...@@ -391,8 +391,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
/* Test */ \ /* Test */ \
V(GetOptimizationStatus) \ V(GetOptimizationStatus) \
V(OptimizeFunctionOnNextCall) \ V(OptimizeFunctionOnNextCall) \
V(OptimizeOsr) \ V(OptimizeOsr)
V(UnblockConcurrentRecompilation)
// Intrinsics with inline versions have to be allowlisted here a second time. // Intrinsics with inline versions have to be allowlisted here a second time.
#define INLINE_INTRINSIC_ALLOWLIST(V) \ #define INLINE_INTRINSIC_ALLOWLIST(V) \
......
...@@ -703,8 +703,6 @@ DEFINE_INT(concurrent_recompilation_queue_length, 8, ...@@ -703,8 +703,6 @@ DEFINE_INT(concurrent_recompilation_queue_length, 8,
"the length of the concurrent compilation queue") "the length of the concurrent compilation queue")
DEFINE_INT(concurrent_recompilation_delay, 0, DEFINE_INT(concurrent_recompilation_delay, 0,
"artificial compilation delay in ms") "artificial compilation delay in ms")
DEFINE_BOOL(block_concurrent_recompilation, false,
"block queued jobs until released")
DEFINE_BOOL(concurrent_inlining, false, DEFINE_BOOL(concurrent_inlining, false,
"run optimizing compiler's inlining phase on a separate thread") "run optimizing compiler's inlining phase on a separate thread")
DEFINE_BOOL(stress_concurrent_inlining, false, DEFINE_BOOL(stress_concurrent_inlining, false,
......
...@@ -569,7 +569,7 @@ RUNTIME_FUNCTION(Runtime_NeverOptimizeFunction) { ...@@ -569,7 +569,7 @@ RUNTIME_FUNCTION(Runtime_NeverOptimizeFunction) {
RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) { RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK(args.length() == 1 || args.length() == 2); DCHECK_EQ(args.length(), 1);
int status = 0; int status = 0;
if (FLAG_lite_mode || FLAG_jitless) { if (FLAG_lite_mode || FLAG_jitless) {
// Both jitless and lite modes cannot optimize. Unit tests should handle // Both jitless and lite modes cannot optimize. Unit tests should handle
...@@ -590,32 +590,8 @@ RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) { ...@@ -590,32 +590,8 @@ RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) {
if (function_object->IsUndefined()) return Smi::FromInt(status); if (function_object->IsUndefined()) return Smi::FromInt(status);
if (!function_object->IsJSFunction()) return CrashUnlessFuzzing(isolate); if (!function_object->IsJSFunction()) return CrashUnlessFuzzing(isolate);
Handle<JSFunction> function = Handle<JSFunction>::cast(function_object); Handle<JSFunction> function = Handle<JSFunction>::cast(function_object);
status |= static_cast<int>(OptimizationStatus::kIsFunction); status |= static_cast<int>(OptimizationStatus::kIsFunction);
bool sync_with_compiler_thread = true;
if (args.length() == 2) {
CONVERT_ARG_HANDLE_CHECKED(Object, sync_object, 1);
if (!sync_object->IsString()) return CrashUnlessFuzzing(isolate);
Handle<String> sync = Handle<String>::cast(sync_object);
if (sync->IsOneByteEqualTo(base::StaticCharVector("no sync"))) {
sync_with_compiler_thread = false;
} else if (sync->IsOneByteEqualTo(base::StaticCharVector("sync")) ||
sync->length() == 0) {
DCHECK(sync_with_compiler_thread);
} else {
return CrashUnlessFuzzing(isolate);
}
}
if (isolate->concurrent_recompilation_enabled() &&
sync_with_compiler_thread) {
while (function->IsInOptimizationQueue()) {
isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions();
base::OS::Sleep(base::TimeDelta::FromMilliseconds(50));
}
}
if (function->IsMarkedForOptimization()) { if (function->IsMarkedForOptimization()) {
status |= static_cast<int>(OptimizationStatus::kMarkedForOptimization); status |= static_cast<int>(OptimizationStatus::kMarkedForOptimization);
} else if (function->IsMarkedForConcurrentOptimization()) { } else if (function->IsMarkedForConcurrentOptimization()) {
...@@ -670,17 +646,8 @@ RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) { ...@@ -670,17 +646,8 @@ RUNTIME_FUNCTION(Runtime_GetOptimizationStatus) {
return Smi::FromInt(status); return Smi::FromInt(status);
} }
RUNTIME_FUNCTION(Runtime_UnblockConcurrentRecompilation) {
DCHECK_EQ(0, args.length());
CHECK(FLAG_block_concurrent_recompilation);
CHECK(isolate->concurrent_recompilation_enabled());
isolate->optimizing_compile_dispatcher()->Unblock();
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_DisableOptimizationFinalization) { RUNTIME_FUNCTION(Runtime_DisableOptimizationFinalization) {
DCHECK_EQ(0, args.length()); DCHECK_EQ(0, args.length());
DCHECK(!FLAG_block_concurrent_recompilation);
CHECK(isolate->concurrent_recompilation_enabled()); CHECK(isolate->concurrent_recompilation_enabled());
isolate->optimizing_compile_dispatcher()->AwaitCompileTasks(); isolate->optimizing_compile_dispatcher()->AwaitCompileTasks();
isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions(); isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions();
...@@ -690,7 +657,6 @@ RUNTIME_FUNCTION(Runtime_DisableOptimizationFinalization) { ...@@ -690,7 +657,6 @@ RUNTIME_FUNCTION(Runtime_DisableOptimizationFinalization) {
RUNTIME_FUNCTION(Runtime_WaitForBackgroundOptimization) { RUNTIME_FUNCTION(Runtime_WaitForBackgroundOptimization) {
DCHECK_EQ(0, args.length()); DCHECK_EQ(0, args.length());
DCHECK(!FLAG_block_concurrent_recompilation);
CHECK(isolate->concurrent_recompilation_enabled()); CHECK(isolate->concurrent_recompilation_enabled());
isolate->optimizing_compile_dispatcher()->AwaitCompileTasks(); isolate->optimizing_compile_dispatcher()->AwaitCompileTasks();
return ReadOnlyRoots(isolate).undefined_value(); return ReadOnlyRoots(isolate).undefined_value();
...@@ -698,7 +664,6 @@ RUNTIME_FUNCTION(Runtime_WaitForBackgroundOptimization) { ...@@ -698,7 +664,6 @@ RUNTIME_FUNCTION(Runtime_WaitForBackgroundOptimization) {
RUNTIME_FUNCTION(Runtime_FinalizeOptimization) { RUNTIME_FUNCTION(Runtime_FinalizeOptimization) {
DCHECK_EQ(0, args.length()); DCHECK_EQ(0, args.length());
DCHECK(!FLAG_block_concurrent_recompilation);
CHECK(isolate->concurrent_recompilation_enabled()); CHECK(isolate->concurrent_recompilation_enabled());
isolate->optimizing_compile_dispatcher()->AwaitCompileTasks(); isolate->optimizing_compile_dispatcher()->AwaitCompileTasks();
isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions(); isolate->optimizing_compile_dispatcher()->InstallOptimizedFunctions();
......
...@@ -488,7 +488,7 @@ namespace internal { ...@@ -488,7 +488,7 @@ namespace internal {
F(FinalizeOptimization, 0, 1) \ F(FinalizeOptimization, 0, 1) \
F(GetCallable, 0, 1) \ F(GetCallable, 0, 1) \
F(GetInitializerFunction, 1, 1) \ F(GetInitializerFunction, 1, 1) \
F(GetOptimizationStatus, -1, 1) \ F(GetOptimizationStatus, 1, 1) \
F(GetUndetectable, 0, 1) \ F(GetUndetectable, 0, 1) \
F(GlobalPrint, 1, 1) \ F(GlobalPrint, 1, 1) \
F(HasDictionaryElements, 1, 1) \ F(HasDictionaryElements, 1, 1) \
...@@ -558,7 +558,6 @@ namespace internal { ...@@ -558,7 +558,6 @@ namespace internal {
F(TraceExit, 1, 1) \ F(TraceExit, 1, 1) \
F(TurbofanStaticAssert, 1, 1) \ F(TurbofanStaticAssert, 1, 1) \
F(TypedArraySpeciesProtector, 0, 1) \ F(TypedArraySpeciesProtector, 0, 1) \
F(UnblockConcurrentRecompilation, 0, 1) \
F(WaitForBackgroundOptimization, 0, 1) \ F(WaitForBackgroundOptimization, 0, 1) \
I(DeoptimizeNow, 0, 1) I(DeoptimizeNow, 0, 1)
......
...@@ -938,7 +938,6 @@ TEST(BreakPointInlinedConstructorBuiltin) { ...@@ -938,7 +938,6 @@ TEST(BreakPointInlinedConstructorBuiltin) {
TEST(BreakPointBuiltinConcurrentOpt) { TEST(BreakPointBuiltinConcurrentOpt) {
i::FLAG_allow_natives_syntax = true; i::FLAG_allow_natives_syntax = true;
i::FLAG_block_concurrent_recompilation = true;
LocalContext env; LocalContext env;
v8::HandleScope scope(env->GetIsolate()); v8::HandleScope scope(env->GetIsolate());
...@@ -952,19 +951,20 @@ TEST(BreakPointBuiltinConcurrentOpt) { ...@@ -952,19 +951,20 @@ TEST(BreakPointBuiltinConcurrentOpt) {
break_point_hit_count = 0; break_point_hit_count = 0;
builtin = CompileRun("Math.sin").As<v8::Function>(); builtin = CompileRun("Math.sin").As<v8::Function>();
CompileRun("function test(x) { return 1 + Math.sin(x) }"); CompileRun("function test(x) { return 1 + Math.sin(x) }");
// Trigger concurrent compile job. It is suspended until unblock.
CompileRun( CompileRun(
"%PrepareFunctionForOptimization(test);" "%PrepareFunctionForOptimization(test);"
"test(0.5); test(0.6);" "test(0.5); test(0.6);"
"%OptimizeFunctionOnNextCall(test, 'concurrent'); test(0.7);"); "%DisableOptimizationFinalization();"
"%OptimizeFunctionOnNextCall(test, 'concurrent');"
"test(0.7);");
CHECK_EQ(0, break_point_hit_count); CHECK_EQ(0, break_point_hit_count);
// Run with breakpoint. // Run with breakpoint.
bp = SetBreakPoint(builtin, 0); bp = SetBreakPoint(builtin, 0);
// Have the concurrent compile job finish now. // Have the concurrent compile job finish now.
CompileRun( CompileRun(
"%UnblockConcurrentRecompilation();" "%FinalizeOptimization();"
"%GetOptimizationStatus(test, 'sync');"); "%GetOptimizationStatus(test);");
CompileRun("test(0.2);"); CompileRun("test(0.2);");
CHECK_EQ(1, break_point_hit_count); CHECK_EQ(1, break_point_hit_count);
...@@ -1446,7 +1446,6 @@ TEST(BreakPointInlineApiFunction) { ...@@ -1446,7 +1446,6 @@ TEST(BreakPointInlineApiFunction) {
// Test that a break point can be set at a return store location. // Test that a break point can be set at a return store location.
TEST(BreakPointConditionBuiltin) { TEST(BreakPointConditionBuiltin) {
i::FLAG_allow_natives_syntax = true; i::FLAG_allow_natives_syntax = true;
i::FLAG_block_concurrent_recompilation = true;
LocalContext env; LocalContext env;
v8::HandleScope scope(env->GetIsolate()); v8::HandleScope scope(env->GetIsolate());
......
...@@ -25,14 +25,9 @@ ...@@ -25,14 +25,9 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --concurrent-recompilation --block-concurrent-recompilation // Flags: --concurrent-recompilation
// Flags: --no-always-opt // Flags: --no-always-opt
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
Debug = debug.Debug; Debug = debug.Debug;
function listener(event, exec_state, event_data, data) { function listener(event, exec_state, event_data, data) {
...@@ -58,19 +53,19 @@ var f = function() { ...@@ -58,19 +53,19 @@ var f = function() {
%PrepareFunctionForOptimization(f); %PrepareFunctionForOptimization(f);
f(); f();
f(); f();
%OptimizeFunctionOnNextCall(f, "concurrent"); // Mark with builtin. %DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(f, "concurrent");
f(); // Kick off concurrent recompilation. f(); // Kick off concurrent recompilation.
%WaitForBackgroundOptimization();
// After compile graph has been created... // After compile graph has been created...
Debug.setListener(listener); // Activate debugger. Debug.setListener(listener); // Activate debugger.
Debug.setBreakPoint(f, 2, 0); // Force deopt. Debug.setBreakPoint(f, 2, 0); // Force deopt.
assertUnoptimized(f);
%FinalizeOptimization();
// At this point, concurrent recompilation is still being blocked. // No optimized code was installed.
assertUnoptimized(f, "no sync"); assertUnoptimized(f);
// Let concurrent recompilation proceed.
%UnblockConcurrentRecompilation();
// Sync with optimization thread. But no optimized code is installed.
assertUnoptimized(f, "sync");
f(); // Trigger break point. f(); // Trigger break point.
assertEquals(1, listened); assertEquals(1, listened);
......
...@@ -25,14 +25,9 @@ ...@@ -25,14 +25,9 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --concurrent-recompilation --block-concurrent-recompilation // Flags: --concurrent-recompilation
// Flags: --no-always-opt // Flags: --no-always-opt
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
Debug = debug.Debug Debug = debug.Debug
function foo() { function foo() {
...@@ -49,6 +44,7 @@ function bar() { ...@@ -49,6 +44,7 @@ function bar() {
foo(); foo();
foo(); foo();
// Mark and kick off recompilation. // Mark and kick off recompilation.
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(foo, "concurrent"); %OptimizeFunctionOnNextCall(foo, "concurrent");
foo(); foo();
...@@ -56,16 +52,14 @@ foo(); ...@@ -56,16 +52,14 @@ foo();
// and (shared) unoptimized code on foo, and sets both to lazy-compile builtin. // and (shared) unoptimized code on foo, and sets both to lazy-compile builtin.
// Clear the break point immediately after to deactivate the debugger. // Clear the break point immediately after to deactivate the debugger.
// Do all of this after compile graph has been created. // Do all of this after compile graph has been created.
%WaitForBackgroundOptimization();
Debug.setListener(function(){}); Debug.setListener(function(){});
Debug.setBreakPoint(bar, 0, 0); Debug.setBreakPoint(bar, 0, 0);
Debug.clearAllBreakPoints(); Debug.clearAllBreakPoints();
Debug.setListener(null); Debug.setListener(null);
assertUnoptimized(foo);
// At this point, concurrent recompilation is still blocked.
assertUnoptimized(foo, "no sync");
// Let concurrent recompilation proceed.
%UnblockConcurrentRecompilation();
// Install optimized code when concurrent optimization finishes. // Install optimized code when concurrent optimization finishes.
// This needs to be able to deal with shared code being a builtin. // This needs to be able to deal with shared code being a builtin.
assertUnoptimized(foo, "sync"); %FinalizeOptimization();
assertUnoptimized(foo);
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --block-concurrent-recompilation
Debug = debug.Debug Debug = debug.Debug
// Test that the side-effect check is not bypassed in optimized code. // Test that the side-effect check is not bypassed in optimized code.
...@@ -60,10 +58,10 @@ function listener(event, exec_state, event_data, data) { ...@@ -60,10 +58,10 @@ function listener(event, exec_state, event_data, data) {
"%OptimizeFunctionOnNextCall(wrapper2); wrapper2(true)"); "%OptimizeFunctionOnNextCall(wrapper2); wrapper2(true)");
%PrepareFunctionForOptimization(wrapper2); %PrepareFunctionForOptimization(wrapper2);
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(wrapper2, "concurrent"); %OptimizeFunctionOnNextCall(wrapper2, "concurrent");
wrapper2(false); wrapper2(false);
fail("%UnblockConcurrentRecompilation();" + fail("%FinalizeOptimization();" +
"%GetOptimizationStatus(wrapper2, 'sync');" +
"wrapper2(true);"); "wrapper2(true);");
} catch (e) { } catch (e) {
exception = e; exception = e;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// Flags: --allow-natives-syntax --no-always-opt --opt // Flags: --allow-natives-syntax --no-always-opt --opt
// Flags: --no-stress-flush-code // Flags: --no-stress-flush-code
// Flags: --no-stress-incremental-marking // Flags: --no-stress-incremental-marking
// Flags: --no-concurrent-recompilation
var source = var source =
` `
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// Flags: --allow-natives-syntax --no-always-opt --opt // Flags: --allow-natives-syntax --no-always-opt --opt
// Flags: --no-stress-flush-code // Flags: --no-stress-flush-code
// Flags: --no-stress-incremental-marking // Flags: --no-stress-incremental-marking
// Flags: --no-concurrent-recompilation
// Flags: --no-baseline-batch-compilation // Flags: --no-baseline-batch-compilation
var source = var source =
......
...@@ -58,7 +58,7 @@ add_field(o); ...@@ -58,7 +58,7 @@ add_field(o);
// Invalidate transition map after compile graph has been created. // Invalidate transition map after compile graph has been created.
%WaitForBackgroundOptimization(); %WaitForBackgroundOptimization();
o.c = 2.2; o.c = 2.2;
assertUnoptimized(add_field, "no sync"); assertUnoptimized(add_field);
// Sync with background thread to conclude optimization that bailed out. // Sync with background thread to conclude optimization that bailed out.
%FinalizeOptimization(); %FinalizeOptimization();
if (!%IsDictPropertyConstTrackingEnabled()) { if (!%IsDictPropertyConstTrackingEnabled()) {
......
...@@ -27,11 +27,6 @@ ...@@ -27,11 +27,6 @@
// Flags: --allow-natives-syntax --no-always-opt --concurrent-recompilation // Flags: --allow-natives-syntax --no-always-opt --concurrent-recompilation
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
function f(foo) { return foo.bar(); } function f(foo) { return foo.bar(); }
%PrepareFunctionForOptimization(f); %PrepareFunctionForOptimization(f);
...@@ -49,7 +44,7 @@ assertEquals(1, f(o)); ...@@ -49,7 +44,7 @@ assertEquals(1, f(o));
// Change the prototype chain after compile graph has been created. // Change the prototype chain after compile graph has been created.
%WaitForBackgroundOptimization(); %WaitForBackgroundOptimization();
o.__proto__.__proto__ = { bar: function() { return 2; } }; o.__proto__.__proto__ = { bar: function() { return 2; } };
assertUnoptimized(f, "no sync"); assertUnoptimized(f);
%FinalizeOptimization(); %FinalizeOptimization();
// Optimization failed due to map dependency. // Optimization failed due to map dependency.
assertUnoptimized(f); assertUnoptimized(f);
......
...@@ -26,14 +26,9 @@ ...@@ -26,14 +26,9 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax --expose-gc // Flags: --allow-natives-syntax --expose-gc
// Flags: --concurrent-recompilation --block-concurrent-recompilation // Flags: --concurrent-recompilation
// Flags: --opt --no-always-opt // Flags: --opt --no-always-opt
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
function f(x) { function f(x) {
var xx = x * x; var xx = x * x;
var xxstr = xx.toString(); var xxstr = xx.toString();
...@@ -55,15 +50,13 @@ f(g(2)); ...@@ -55,15 +50,13 @@ f(g(2));
assertUnoptimized(f); assertUnoptimized(f);
assertUnoptimized(g); assertUnoptimized(g);
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(f, "concurrent"); %OptimizeFunctionOnNextCall(f, "concurrent");
%OptimizeFunctionOnNextCall(g, "concurrent"); %OptimizeFunctionOnNextCall(g, "concurrent");
f(g(3)); // Kick off recompilation. f(g(3));
assertUnoptimized(f, 'no sync'); // Not yet optimized since recompilation
assertUnoptimized(g, 'no sync'); // is still blocked.
// Let concurrent recompilation proceed. assertUnoptimized(f);
%UnblockConcurrentRecompilation(); assertUnoptimized(g);
%FinalizeOptimization();
assertOptimized(f, 'sync'); // Optimized once we sync with the assertOptimized(f);
assertOptimized(g, 'sync'); // background thread. assertOptimized(g);
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --allow-natives-syntax --opt --no-always-opt // Flags: --allow-natives-syntax --opt --no-always-opt
// Flags: --concurrent-recompilation --block-concurrent-recompilation // Flags: --concurrent-recompilation
function foo(x) { bar(x) } function foo(x) { bar(x) }
function bar(x) { x.p } function bar(x) { x.p }
...@@ -27,19 +27,21 @@ foo(a); ...@@ -27,19 +27,21 @@ foo(a);
foo(a); foo(a);
// Trigger optimization of bar but don't yet complete it. // Trigger optimization of bar but don't yet complete it.
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(bar, "concurrent"); %OptimizeFunctionOnNextCall(bar, "concurrent");
foo(a); foo(a);
%PrepareFunctionForOptimization(bar); %PrepareFunctionForOptimization(bar);
%WaitForBackgroundOptimization();
// Change a's map from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS and run bar in the // Change a's map from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS and run bar in the
// interpreter (via foo) s.t. bar's load feedback changes accordingly. // interpreter (via foo) s.t. bar's load feedback changes accordingly.
a[0] = {}; a[0] = {};
foo(a); foo(a);
assertUnoptimized(bar, "no sync"); assertUnoptimized(bar);
// Now finish the optimization of bar, which was based on the old // Now finish the optimization of bar, which was based on the old
// PACKED_SMI_ELEMENTS feedback. // PACKED_SMI_ELEMENTS feedback.
%UnblockConcurrentRecompilation(); %FinalizeOptimization();
assertOptimized(bar); assertOptimized(bar);
// If we were to call the optimized bar now, it would deopt. // If we were to call the optimized bar now, it would deopt.
......
...@@ -25,11 +25,9 @@ ...@@ -25,11 +25,9 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax // Flags: --allow-natives-syntax --concurrent-recompilation
// Flags: --concurrent-recompilation // Flags: --no-stress-opt --no-always-opt --no-turboprop
// Flags: --nostress-opt --no-always-opt //
// Flags: --no-turboprop
// --nostress-opt is in place because this particular optimization // --nostress-opt is in place because this particular optimization
// (guaranteeing that the Array prototype chain has no elements) is // (guaranteeing that the Array prototype chain has no elements) is
// maintained isolate-wide. Once it's been "broken" by the change // maintained isolate-wide. Once it's been "broken" by the change
...@@ -37,11 +35,6 @@ ...@@ -37,11 +35,6 @@
// optimization anymore, and the code will remain optimized despite // optimization anymore, and the code will remain optimized despite
// additional changes to the prototype chain. // additional changes to the prototype chain.
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
function f1(a, i) { function f1(a, i) {
return a[i] + 0.5; return a[i] + 0.5;
} }
...@@ -62,7 +55,7 @@ assertEquals(0.5, f1(arr, 0)); ...@@ -62,7 +55,7 @@ assertEquals(0.5, f1(arr, 0));
%WaitForBackgroundOptimization(); %WaitForBackgroundOptimization();
Object.prototype[1] = 1.5; Object.prototype[1] = 1.5;
assertEquals(2, f1(arr, 1)); assertEquals(2, f1(arr, 1));
assertUnoptimized(f1, "no sync"); assertUnoptimized(f1);
// Sync with background thread to conclude optimization, which bails out // Sync with background thread to conclude optimization, which bails out
// due to map dependency. // due to map dependency.
%FinalizeOptimization(); %FinalizeOptimization();
......
...@@ -25,8 +25,7 @@ ...@@ -25,8 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax // Flags: --allow-natives-syntax --concurrent-recompilation
// Flags: --concurrent-recompilation --block-concurrent-recompilation
// Flags: --nostress-opt --no-always-opt // Flags: --nostress-opt --no-always-opt
// --nostress-opt is in place because this particular optimization // --nostress-opt is in place because this particular optimization
...@@ -36,11 +35,6 @@ ...@@ -36,11 +35,6 @@
// optimization anymore, and the code will remain optimized despite // optimization anymore, and the code will remain optimized despite
// additional changes to the prototype chain. // additional changes to the prototype chain.
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
function f1(a, i) { function f1(a, i) {
return a[i] + 0.5; return a[i] + 0.5;
} }
...@@ -51,19 +45,21 @@ assertEquals(0.5, f1(arr, 0)); ...@@ -51,19 +45,21 @@ assertEquals(0.5, f1(arr, 0));
assertEquals(0.5, f1(arr, 0)); assertEquals(0.5, f1(arr, 0));
// Optimized code of f1 depends on initial object and array maps. // Optimized code of f1 depends on initial object and array maps.
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(f1, "concurrent"); %OptimizeFunctionOnNextCall(f1, "concurrent");
// Kick off recompilation. // Kick off recompilation.
assertEquals(0.5, f1(arr, 0)); assertEquals(0.5, f1(arr, 0));
%WaitForBackgroundOptimization();
// Invalidate current initial object map. // Invalidate current initial object map.
Object.prototype[1] = 1.5; Object.prototype[1] = 1.5;
assertEquals(2, f1(arr, 1)); assertEquals(2, f1(arr, 1));
// Not yet optimized since concurrent recompilation is blocked. // Not yet optimized since concurrent recompilation is blocked.
assertUnoptimized(f1, "no sync"); assertUnoptimized(f1);
// Let concurrent recompilation proceed. // Sync with background thread to conclude optimization, which does bailout due
%UnblockConcurrentRecompilation(); // to map dependency, because the compiler read the NoElements protector before
// Sync with background thread to conclude optimization, which may or may not // the store to Object.prototype above.
// bailout due to map dependency, depending on whether the compiler read the %FinalizeOptimization();
// NoElements protector before or after the store to Object.prototype above. assertUnoptimized(f1);
assertEquals(2, f1(arr, 1)); assertEquals(2, f1(arr, 1));
// Clear type info for stress runs. // Clear type info for stress runs.
%ClearFunctionFeedback(f1); %ClearFunctionFeedback(f1);
...@@ -715,7 +715,7 @@ function testbench(o, proto, update_proto, check_constness) { ...@@ -715,7 +715,7 @@ function testbench(o, proto, update_proto, check_constness) {
%DisableOptimizationFinalization(); %DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(read_length, "concurrent"); %OptimizeFunctionOnNextCall(read_length, "concurrent");
assertEquals(1, read_length(o)); assertEquals(1, read_length(o));
assertUnoptimized(read_length, "no sync"); assertUnoptimized(read_length);
%WaitForBackgroundOptimization(); %WaitForBackgroundOptimization();
var other_proto1 = []; var other_proto1 = [];
......
...@@ -132,9 +132,7 @@ var assertInstanceof; ...@@ -132,9 +132,7 @@ var assertInstanceof;
// Assert that this code is never executed (i.e., always fails if executed). // Assert that this code is never executed (i.e., always fails if executed).
var assertUnreachable; var assertUnreachable;
// Assert that the function code is (not) optimized. If "no sync" is passed // Assert that the function code is (not) optimized.
// as second argument, we do not wait for the concurrent optimization thread to
// finish when polling for optimization status.
// Only works with --allow-natives-syntax. // Only works with --allow-natives-syntax.
var assertOptimized; var assertOptimized;
var assertUnoptimized; var assertUnoptimized;
...@@ -657,22 +655,21 @@ var prettyPrinted; ...@@ -657,22 +655,21 @@ var prettyPrinted;
var OptimizationStatusImpl = undefined; var OptimizationStatusImpl = undefined;
var OptimizationStatus = function(fun, sync_opt) { var OptimizationStatus = function(fun) {
if (OptimizationStatusImpl === undefined) { if (OptimizationStatusImpl === undefined) {
try { try {
OptimizationStatusImpl = new Function( OptimizationStatusImpl = new Function(
"fun", "sync", "return %GetOptimizationStatus(fun, sync);"); "fun", "return %GetOptimizationStatus(fun);");
} catch (e) { } catch (e) {
throw new Error("natives syntax not allowed"); throw new Error("natives syntax not allowed");
} }
} }
return OptimizationStatusImpl(fun, sync_opt); return OptimizationStatusImpl(fun);
} }
assertUnoptimized = function assertUnoptimized( assertUnoptimized = function assertUnoptimized(
fun, sync_opt, name_opt, skip_if_maybe_deopted = true) { fun, name_opt, skip_if_maybe_deopted = true) {
if (sync_opt === undefined) sync_opt = ""; var opt_status = OptimizationStatus(fun);
var opt_status = OptimizationStatus(fun, sync_opt);
// Tests that use assertUnoptimized() do not make sense if --always-opt // Tests that use assertUnoptimized() do not make sense if --always-opt
// option is provided. Such tests must add --no-always-opt to flags comment. // option is provided. Such tests must add --no-always-opt to flags comment.
assertFalse((opt_status & V8OptimizationStatus.kAlwaysOptimize) !== 0, assertFalse((opt_status & V8OptimizationStatus.kAlwaysOptimize) !== 0,
...@@ -690,9 +687,8 @@ var prettyPrinted; ...@@ -690,9 +687,8 @@ var prettyPrinted;
} }
assertOptimized = function assertOptimized( assertOptimized = function assertOptimized(
fun, sync_opt, name_opt, skip_if_maybe_deopted = true) { fun, name_opt, skip_if_maybe_deopted = true) {
if (sync_opt === undefined) sync_opt = ""; var opt_status = OptimizationStatus(fun);
var opt_status = OptimizationStatus(fun, sync_opt);
// Tests that use assertOptimized() do not make sense for Lite mode where // Tests that use assertOptimized() do not make sense for Lite mode where
// optimization is always disabled, explicitly exit the test with a warning. // optimization is always disabled, explicitly exit the test with a warning.
if (opt_status & V8OptimizationStatus.kLiteMode) { if (opt_status & V8OptimizationStatus.kLiteMode) {
......
...@@ -1085,18 +1085,6 @@ ...@@ -1085,18 +1085,6 @@
# BUG(v8:7166). # BUG(v8:7166).
'd8/enable-tracing': [SKIP], 'd8/enable-tracing': [SKIP],
# Rely on (blocking) concurrent compilation.
'compiler/concurrent-invalidate-transition-map': [SKIP],
'compiler/concurrent-proto-change': [SKIP],
'compiler/manual-concurrent-recompile': [SKIP],
'compiler/regress-905555-2': [SKIP],
'compiler/regress-905555': [SKIP],
'compiler/regress-9945-1': [SKIP],
'concurrent-initial-prototype-change-1': [SKIP],
'concurrent-initial-prototype-change-2': [SKIP],
'regress/regress-356053': [SKIP],
'regress/regress-embedded-cons-string': [SKIP],
# Intentionally non-deterministic using shared arraybuffers between workers. # Intentionally non-deterministic using shared arraybuffers between workers.
'wasm/atomics-stress': [SKIP], 'wasm/atomics-stress': [SKIP],
'wasm/atomics64-stress': [SKIP], 'wasm/atomics64-stress': [SKIP],
...@@ -1512,7 +1500,6 @@ ...@@ -1512,7 +1500,6 @@
'compiler/regress-9945-1': [SKIP], 'compiler/regress-9945-1': [SKIP],
'concurrent-initial-prototype-change-1': [SKIP], 'concurrent-initial-prototype-change-1': [SKIP],
'concurrent-initial-prototype-change-2': [SKIP], 'concurrent-initial-prototype-change-2': [SKIP],
'regress/regress-356053': [SKIP],
'regress/regress-embedded-cons-string': [SKIP], 'regress/regress-embedded-cons-string': [SKIP],
# Requires a second isolate # Requires a second isolate
'regress/regress-1212404': [SKIP], 'regress/regress-1212404': [SKIP],
......
...@@ -4,9 +4,12 @@ ...@@ -4,9 +4,12 @@
// //
// Flags: --allow-natives-syntax // Flags: --allow-natives-syntax
var no_sync_uninternalized = "no " + "sync"; var s_uninternalized = "concurrent" + "-skip-finalization";
%InternalizeString(no_sync_uninternalized); %InternalizeString(s_uninternalized);
// Make sure %GetOptimizationStatus works with a non-internalized string function foo() {}
// parameter.
%GetOptimizationStatus(function() {}, no_sync_uninternalized) // Make sure %OptimizeFunctionOnNextCall works with a non-internalized
// string parameter.
%PrepareFunctionForOptimization(foo);
%OptimizeFunctionOnNextCall(foo, s_uninternalized)
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --allow-natives-syntax --block-concurrent-recompilation // Flags: --allow-natives-syntax --expose-gc
// Flags: --expose-gc
function Ctor() { function Ctor() {
this.a = 1; this.a = 1;
......
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-gc --allow-natives-syntax
// Flags: --concurrent-recompilation --block-concurrent-recompilation
gc();
try { %UnblockConcurrentRecompilation(); } catch (e) { }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --allow-natives-syntax --expose-gc --block-concurrent-recompilation // Flags: --allow-natives-syntax --expose-gc
function Inner() { function Inner() {
this.property = "OK"; this.property = "OK";
...@@ -36,8 +36,10 @@ SetInner(outer, inner); ...@@ -36,8 +36,10 @@ SetInner(outer, inner);
// on the compiler thread :-) // on the compiler thread :-)
KeepMapAlive(outer); KeepMapAlive(outer);
KeepMapAlive(outer); KeepMapAlive(outer);
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(KeepMapAlive, "concurrent"); %OptimizeFunctionOnNextCall(KeepMapAlive, "concurrent");
KeepMapAlive(outer); KeepMapAlive(outer);
%WaitForBackgroundOptimization();
// So far, all is well. Collect type feedback and optimize. // So far, all is well. Collect type feedback and optimize.
print(Crash(outer)); print(Crash(outer));
......
...@@ -25,31 +25,26 @@ ...@@ -25,31 +25,26 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --expose-gc --allow-natives-syntax // Flags: --expose-gc --allow-natives-syntax --concurrent-recompilation
// Flags: --concurrent-recompilation --block-concurrent-recompilation
// Flags: --opt --no-always-opt // Flags: --opt --no-always-opt
if (!%IsConcurrentRecompilationSupported()) {
print("Concurrent recompilation is disabled. Skipping this test.");
quit();
}
function test(fun) { function test(fun) {
%PrepareFunctionForOptimization(fun); %PrepareFunctionForOptimization(fun);
fun(); fun();
fun(); fun();
// Mark for concurrent optimization. %DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(fun, "concurrent"); %OptimizeFunctionOnNextCall(fun, "concurrent");
// Kick off recompilation. // Kick off recompilation.
fun(); fun();
// Tenure cons string after compile graph has been created. // Tenure cons string after compile graph has been created.
%WaitForBackgroundOptimization();
gc(); gc();
// In the mean time, concurrent recompiling is still blocked. // In the mean time, concurrent recompiling is still blocked.
assertUnoptimized(fun, "no sync"); assertUnoptimized(fun);
// Let concurrent recompilation proceed. // Let concurrent recompilation finish.
%UnblockConcurrentRecompilation(); %FinalizeOptimization();
// Concurrent recompilation eventually finishes, embeds tenured cons string. // Concurrent recompilation eventually finishes, embeds tenured cons string.
assertOptimized(fun, "sync"); assertOptimized(fun);
// Visit embedded cons string during mark compact. // Visit embedded cons string during mark compact.
gc(); gc();
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --allow-natives-syntax --block-concurrent-recompilation // Flags: --allow-natives-syntax
function Ctor() { function Ctor() {
this.a = 1; this.a = 1;
......
...@@ -26,11 +26,13 @@ for (var i = 0; i < 100; ++i) { ...@@ -26,11 +26,13 @@ for (var i = 0; i < 100; ++i) {
%PrepareFunctionForOptimization(testConcurrent); %PrepareFunctionForOptimization(testConcurrent);
testConcurrent(0.5); testConcurrent(0.5);
testConcurrent(0.6); testConcurrent(0.6);
%DisableOptimizationFinalization();
%OptimizeFunctionOnNextCall(testConcurrent, 'concurrent'); %OptimizeFunctionOnNextCall(testConcurrent, 'concurrent');
for (var i = 0; i < 100; ++i) { for (var i = 0; i < 100; ++i) {
testConcurrent(0.7); testConcurrent(0.7);
} }
%GetOptimizationStatus(testConcurrent, 'sync'); %FinalizeOptimization();
%GetOptimizationStatus(testConcurrent);
gc(); gc();
...@@ -40,6 +40,6 @@ print("Sensitive runtime functions are neutered"); ...@@ -40,6 +40,6 @@ print("Sensitive runtime functions are neutered");
%OptimizeFunctionOnNextCall(foo); %OptimizeFunctionOnNextCall(foo);
foo(); foo();
print(%GetOptimizationStatus(foo)); print(%GetOptimizationStatus(foo));
const fun = new Function("f", "sync", "return %GetOptimizationStatus(f);"); const fun = new Function("f", "return %GetOptimizationStatus(f);");
print(fun(foo)); print(fun(foo));
})(); })();
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