Commit 80195fc5 authored by Ross McIlroy's avatar Ross McIlroy Committed by Commit Bot

[Compile] Refactor CompilerDispatcher for inner function compilation jobs

Refactors the CompilerDispatcher to be able to enqueue eager inner functions
for off-thread compilation during top-level compilation of a script.

Unoptimized compile jobs are simplified to only have two phases - compile
and finalization. Only finalization requires heap access (and therefore
needs to be run on the main thread). The change also introduces a requirement
to register a SFI with a given compile job after that job is posted, this
is due to the fact that an SFI won't necessarily exist at the point the job
is posted, but is created later when top-level compile is being finalized.
Logic in the compile dispatcher is update to deal with the fact that a job
may not be able to progress if it doesn't yet have an associated SFI
registered with it.

BUG=v8:8041

Change-Id: I66cccd626136738304a7cab0e501fc65cf342514
Reviewed-on: https://chromium-review.googlesource.com/1215782
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarMarja Hölttä <marja@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#56088}
parent a50baa24
...@@ -242,6 +242,17 @@ const AstRawString* AstValueFactory::GetString(Handle<String> literal) { ...@@ -242,6 +242,17 @@ const AstRawString* AstValueFactory::GetString(Handle<String> literal) {
return result; return result;
} }
const AstRawString* AstValueFactory::CloneFromOtherFactory(
const AstRawString* raw_string) {
const AstRawString* result = GetString(
raw_string->hash_field(), raw_string->is_one_byte(),
Vector<const byte>(raw_string->raw_data(), raw_string->byte_length()));
// Check we weren't trying to clone a string that was already in this
// ast-value-factory.
DCHECK_NE(result, raw_string);
return result;
}
AstConsString* AstValueFactory::NewConsString() { AstConsString* AstValueFactory::NewConsString() {
AstConsString* new_string = new (zone_) AstConsString; AstConsString* new_string = new (zone_) AstConsString;
DCHECK_NOT_NULL(new_string); DCHECK_NOT_NULL(new_string);
......
...@@ -297,6 +297,11 @@ class AstValueFactory { ...@@ -297,6 +297,11 @@ class AstValueFactory {
return GetTwoByteStringInternal(literal); return GetTwoByteStringInternal(literal);
} }
const AstRawString* GetString(Handle<String> literal); const AstRawString* GetString(Handle<String> literal);
// Clones an AstRawString from another ast value factory, adding it to this
// factory and returning the clone.
const AstRawString* CloneFromOtherFactory(const AstRawString* raw_string);
V8_EXPORT_PRIVATE AstConsString* NewConsString(); V8_EXPORT_PRIVATE AstConsString* NewConsString();
V8_EXPORT_PRIVATE AstConsString* NewConsString(const AstRawString* str); V8_EXPORT_PRIVATE AstConsString* NewConsString(const AstRawString* str);
V8_EXPORT_PRIVATE AstConsString* NewConsString(const AstRawString* str1, V8_EXPORT_PRIVATE AstConsString* NewConsString(const AstRawString* str1,
......
...@@ -2261,7 +2261,7 @@ class FunctionLiteral final : public Expression { ...@@ -2261,7 +2261,7 @@ class FunctionLiteral final : public Expression {
void mark_as_iife() { bit_field_ = IIFEBit::update(bit_field_, true); } void mark_as_iife() { bit_field_ = IIFEBit::update(bit_field_, true); }
bool is_iife() const { return IIFEBit::decode(bit_field_); } bool is_iife() const { return IIFEBit::decode(bit_field_); }
bool is_top_level() const { bool is_toplevel() const {
return function_literal_id() == FunctionLiteral::kIdTypeTopLevel; return function_literal_id() == FunctionLiteral::kIdTypeTopLevel;
} }
bool is_wrapped() const { return function_type() == kWrapped; } bool is_wrapped() const { return function_type() == kWrapped; }
......
...@@ -21,9 +21,7 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob { ...@@ -21,9 +21,7 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob {
enum class Status { enum class Status {
kInitial, kInitial,
kPrepared, kReadyToFinalize,
kCompiled,
kHasErrorsToReport,
kDone, kDone,
kFailed, kFailed,
}; };
...@@ -48,26 +46,19 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob { ...@@ -48,26 +46,19 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob {
// Return true if the next step can be run on any thread. // Return true if the next step can be run on any thread.
bool NextStepCanRunOnAnyThread() const { bool NextStepCanRunOnAnyThread() const {
return status() == Status::kPrepared; return status() == Status::kInitial;
} }
// Casts to implementations. // Casts to implementations.
const UnoptimizedCompileJob* AsUnoptimizedCompileJob() const; const UnoptimizedCompileJob* AsUnoptimizedCompileJob() const;
// Transition from kInitial to kPrepared. Must only be invoked on the // Transition from kInitial to kReadyToFinalize.
// main thread.
virtual void PrepareOnMainThread(Isolate* isolate) = 0;
// Transition from kPrepared to kCompiled (or kReportErrors).
virtual void Compile(bool on_background_thread) = 0; virtual void Compile(bool on_background_thread) = 0;
// Transition from kCompiled to kDone (or kFailed). Must only be invoked on // Transition from kReadyToFinalize to kDone (or kFailed). Must only be
// the main thread. // invoked on the main thread.
virtual void FinalizeOnMainThread(Isolate* isolate) = 0; virtual void FinalizeOnMainThread(Isolate* isolate,
Handle<SharedFunctionInfo> shared) = 0;
// Transition from kReportErrors to kFailed. Must only be invoked on the main
// thread.
virtual void ReportErrorsOnMainThread(Isolate* isolate) = 0;
// Free all resources. Must only be invoked on the main thread. // Free all resources. Must only be invoked on the main thread.
virtual void ResetOnMainThread(Isolate* isolate) = 0; virtual void ResetOnMainThread(Isolate* isolate) = 0;
...@@ -75,9 +66,6 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob { ...@@ -75,9 +66,6 @@ class V8_EXPORT_PRIVATE CompilerDispatcherJob {
// Estimate how long the next step will take using the tracer. // Estimate how long the next step will take using the tracer.
virtual double EstimateRuntimeOfNextStepInMs() const = 0; virtual double EstimateRuntimeOfNextStepInMs() const = 0;
// Print short description of job. Must only be invoked on the main thread.
virtual void ShortPrintOnMainThread() = 0;
protected: protected:
void set_status(Status status) { status_ = status; } void set_status(Status status) { status_ = status; }
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
#include "src/compiler-dispatcher/compiler-dispatcher.h" #include "src/compiler-dispatcher/compiler-dispatcher.h"
#include "include/v8-platform.h" #include "src/ast/ast.h"
#include "include/v8.h"
#include "src/base/platform/time.h" #include "src/base/platform/time.h"
#include "src/base/template-utils.h" #include "src/base/template-utils.h"
#include "src/cancelable-task.h" #include "src/cancelable-task.h"
...@@ -13,6 +12,7 @@ ...@@ -13,6 +12,7 @@
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" #include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/compiler-dispatcher/unoptimized-compile-job.h" #include "src/compiler-dispatcher/unoptimized-compile-job.h"
#include "src/flags.h" #include "src/flags.h"
#include "src/global-handles.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
namespace v8 { namespace v8 {
...@@ -22,47 +22,17 @@ namespace { ...@@ -22,47 +22,17 @@ namespace {
enum class ExceptionHandling { kSwallow, kThrow }; enum class ExceptionHandling { kSwallow, kThrow };
bool DoNextStepOnMainThread(Isolate* isolate, CompilerDispatcherJob* job, void FinalizeJobOnMainThread(Isolate* isolate, CompilerDispatcherJob* job,
ExceptionHandling exception_handling) { Handle<SharedFunctionInfo> shared,
ExceptionHandling exception_handling) {
DCHECK(ThreadId::Current().Equals(isolate->thread_id())); DCHECK(ThreadId::Current().Equals(isolate->thread_id()));
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), DCHECK_EQ(job->status(), CompilerDispatcherJob::Status::kReadyToFinalize);
"V8.CompilerDispatcherForgroundStep");
switch (job->status()) {
case CompilerDispatcherJob::Status::kInitial:
job->PrepareOnMainThread(isolate);
break;
case CompilerDispatcherJob::Status::kPrepared:
job->Compile(false);
break;
case CompilerDispatcherJob::Status::kCompiled:
job->FinalizeOnMainThread(isolate);
break;
case CompilerDispatcherJob::Status::kHasErrorsToReport:
job->ReportErrorsOnMainThread(isolate);
break;
case CompilerDispatcherJob::Status::kFailed:
case CompilerDispatcherJob::Status::kDone:
UNREACHABLE();
}
job->FinalizeOnMainThread(isolate, shared);
DCHECK_EQ(job->IsFailed(), isolate->has_pending_exception()); DCHECK_EQ(job->IsFailed(), isolate->has_pending_exception());
if (job->IsFailed() && exception_handling == ExceptionHandling::kSwallow) { if (job->IsFailed() && exception_handling == ExceptionHandling::kSwallow) {
isolate->clear_pending_exception(); isolate->clear_pending_exception();
} }
return job->IsFailed();
}
void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
DCHECK(job->NextStepCanRunOnAnyThread());
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherBackgroundStep");
switch (job->status()) {
case CompilerDispatcherJob::Status::kPrepared:
job->Compile(true);
break;
default:
UNREACHABLE();
}
} }
// Theoretically we get 50ms of idle time max, however it's unlikely that // Theoretically we get 50ms of idle time max, however it's unlikely that
...@@ -173,6 +143,9 @@ void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) { ...@@ -173,6 +143,9 @@ void CompilerDispatcher::IdleTask::RunInternal(double deadline_in_seconds) {
CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
size_t max_stack_size) size_t max_stack_size)
: isolate_(isolate), : isolate_(isolate),
allocator_(isolate->allocator()),
worker_thread_runtime_call_stats_(
isolate->counters()->worker_thread_runtime_call_stats()),
platform_(platform), platform_(platform),
max_stack_size_(max_stack_size), max_stack_size_(max_stack_size),
trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher), trace_compiler_dispatcher_(FLAG_trace_compiler_dispatcher),
...@@ -201,6 +174,8 @@ CompilerDispatcher::~CompilerDispatcher() { ...@@ -201,6 +174,8 @@ CompilerDispatcher::~CompilerDispatcher() {
bool CompilerDispatcher::CanEnqueue() { bool CompilerDispatcher::CanEnqueue() {
if (!IsEnabled()) return false; if (!IsEnabled()) return false;
// TODO(rmcilroy): Investigate if MemoryPressureLevel::kNone is ever sent on
// Android, if not, remove this check.
if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) { if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) {
return false; return false;
} }
...@@ -213,86 +188,64 @@ bool CompilerDispatcher::CanEnqueue() { ...@@ -213,86 +188,64 @@ bool CompilerDispatcher::CanEnqueue() {
return true; return true;
} }
bool CompilerDispatcher::CanEnqueue(Handle<SharedFunctionInfo> function) { base::Optional<CompilerDispatcher::JobId> CompilerDispatcher::Enqueue(
if (!CanEnqueue()) return false; const ParseInfo* outer_parse_info, const AstRawString* function_name,
const FunctionLiteral* function_literal) {
// We only handle functions (no eval / top-level code / native) that are TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
// attached to a script. "V8.CompilerDispatcherEnqueue");
if (!function->script()->IsScript() || function->is_toplevel() || RuntimeCallTimerScope runtimeTimer(
function->native()) { isolate_, RuntimeCallCounterId::kCompileEnqueueOnDispatcher);
return false;
}
return true;
}
CompilerDispatcher::JobId CompilerDispatcher::Enqueue( if (!CanEnqueue()) return base::nullopt;
std::unique_ptr<CompilerDispatcherJob> job) {
DCHECK(!job->IsFinished());
JobMap::const_iterator it = InsertJob(std::move(job));
ConsiderJobForBackgroundProcessing(it->second.get());
ScheduleIdleTaskIfNeeded();
return it->first;
}
CompilerDispatcher::JobId CompilerDispatcher::EnqueueAndStep( std::unique_ptr<CompilerDispatcherJob> job(new UnoptimizedCompileJob(
std::unique_ptr<CompilerDispatcherJob> job) { tracer_.get(), allocator_, outer_parse_info, function_name,
DCHECK(!job->IsFinished()); function_literal, worker_thread_runtime_call_stats_, max_stack_size_));
JobMap::const_iterator it = InsertJob(std::move(job)); JobMap::const_iterator it = InsertJob(std::move(job));
JobId id = it->first;
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: stepping "); PrintF("CompilerDispatcher: enqueued job %zu for function literal id %d\n",
it->second->ShortPrintOnMainThread(); id, function_literal->function_literal_id());
PrintF("\n");
} }
DoNextStepOnMainThread(isolate_, it->second.get(),
ExceptionHandling::kSwallow); // Post a idle task and a background worker task to perform the compilation
// either on the worker thread or during idle time (whichever is first).
ConsiderJobForBackgroundProcessing(it->second.get()); ConsiderJobForBackgroundProcessing(it->second.get());
RemoveIfFinished(it);
ScheduleIdleTaskIfNeeded(); ScheduleIdleTaskIfNeeded();
return it->first; return base::make_optional(id);
} }
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; }
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherEnqueue");
if (!CanEnqueue(function)) return false;
if (IsEnqueued(function)) return true;
if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: enqueuing ");
function->ShortPrint();
PrintF(" for parse and compile\n");
}
std::unique_ptr<CompilerDispatcherJob> job(new UnoptimizedCompileJob( bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
isolate_, tracer_.get(), function, max_stack_size_)); if (jobs_.empty()) return false;
Enqueue(std::move(job)); return GetJobFor(function) != jobs_.end();
return true;
} }
bool CompilerDispatcher::EnqueueAndStep(Handle<SharedFunctionInfo> function) { bool CompilerDispatcher::IsEnqueued(JobId job_id) const {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), return jobs_.find(job_id) != jobs_.end();
"V8.CompilerDispatcherEnqueueAndStep"); }
if (!CanEnqueue(function)) return false; void CompilerDispatcher::RegisterSharedFunctionInfo(
if (IsEnqueued(function)) return true; JobId job_id, SharedFunctionInfo* function) {
DCHECK_NE(jobs_.find(job_id), jobs_.end());
DCHECK_EQ(job_id_to_shared_.find(job_id), job_id_to_shared_.end());
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: enqueuing "); PrintF("CompilerDispatcher: registering ");
function->ShortPrint(); function->ShortPrint();
PrintF(" for parse and compile\n"); PrintF(" with job id %zu\n", job_id);
} }
std::unique_ptr<CompilerDispatcherJob> job(new UnoptimizedCompileJob( // Make a global handle to the function.
isolate_, tracer_.get(), function, max_stack_size_)); Handle<SharedFunctionInfo> function_handle =
EnqueueAndStep(std::move(job)); isolate_->global_handles()->Create(function);
return true;
}
bool CompilerDispatcher::IsEnabled() const { return FLAG_compiler_dispatcher; } // Register mapping.
job_id_to_shared_.insert(std::make_pair(job_id, function_handle));
shared_to_unoptimized_job_id_.Set(function_handle, job_id);
bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const { // Schedule an idle task to finalize job if it is ready.
if (jobs_.empty()) return false; ScheduleIdleTaskIfNeeded();
return GetJobFor(function) != jobs_.end();
} }
void CompilerDispatcher::WaitForJobIfRunningOnBackground( void CompilerDispatcher::WaitForJobIfRunningOnBackground(
...@@ -316,54 +269,41 @@ void CompilerDispatcher::WaitForJobIfRunningOnBackground( ...@@ -316,54 +269,41 @@ void CompilerDispatcher::WaitForJobIfRunningOnBackground(
DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end()); DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end());
} }
bool CompilerDispatcher::FinishNow(CompilerDispatcherJob* job) { bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherFinishNow");
RuntimeCallTimerScope runtimeTimer(
isolate_, RuntimeCallCounterId::kCompileFinishNowOnDispatcher);
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: finishing "); PrintF("CompilerDispatcher: finishing ");
job->ShortPrintOnMainThread(); function->ShortPrint();
PrintF(" now\n"); PrintF(" now\n");
} }
JobMap::const_iterator it = GetJobFor(function);
CHECK(it != jobs_.end());
CompilerDispatcherJob* job = it->second.get();
WaitForJobIfRunningOnBackground(job); WaitForJobIfRunningOnBackground(job);
while (!job->IsFinished()) { while (!job->IsFinished()) {
DoNextStepOnMainThread(isolate_, job, ExceptionHandling::kThrow); switch (job->status()) {
} case CompilerDispatcherJob::Status::kInitial:
return !job->IsFailed(); job->Compile(false);
} break;
case CompilerDispatcherJob::Status::kReadyToFinalize: {
bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) { FinalizeJobOnMainThread(isolate_, job, function,
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"), ExceptionHandling::kThrow);
"V8.CompilerDispatcherFinishNow"); break;
JobMap::const_iterator job = GetJobFor(function);
CHECK(job != jobs_.end());
bool result = FinishNow(job->second.get());
RemoveIfFinished(job);
return result;
}
void CompilerDispatcher::FinishAllNow() {
// First finish all jobs not running in background
for (auto it = jobs_.cbegin(); it != jobs_.cend();) {
CompilerDispatcherJob* job = it->second.get();
bool is_running_in_background;
{
base::LockGuard<base::Mutex> lock(&mutex_);
is_running_in_background =
running_background_jobs_.find(job) != running_background_jobs_.end();
pending_background_jobs_.erase(job);
}
if (!is_running_in_background) {
while (!job->IsFinished()) {
DoNextStepOnMainThread(isolate_, job, ExceptionHandling::kThrow);
} }
it = RemoveIfFinished(it); case CompilerDispatcherJob::Status::kFailed:
} else { case CompilerDispatcherJob::Status::kDone:
++it; UNREACHABLE();
} }
} }
// Potentially wait for jobs that were running in background DCHECK_EQ(job->IsFailed(), isolate_->has_pending_exception());
for (auto it = jobs_.cbegin(); it != jobs_.cend(); DCHECK(job->IsFinished());
it = RemoveIfFinished(it)) { bool result = !job->IsFailed();
FinishNow(it->second.get()); RemoveJob(it);
} return result;
} }
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
...@@ -373,9 +313,7 @@ void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { ...@@ -373,9 +313,7 @@ void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
for (auto& it : jobs_) { for (auto& it : jobs_) {
WaitForJobIfRunningOnBackground(it.second.get()); WaitForJobIfRunningOnBackground(it.second.get());
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: aborted "); PrintF("CompilerDispatcher: aborted job %zu\n", it.first);
it.second->ShortPrintOnMainThread();
PrintF("\n");
} }
it.second->ResetOnMainThread(isolate_); it.second->ResetOnMainThread(isolate_);
} }
...@@ -394,6 +332,7 @@ void CompilerDispatcher::AbortAll(BlockingBehavior blocking) { ...@@ -394,6 +332,7 @@ void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
base::LockGuard<base::Mutex> lock(&mutex_); base::LockGuard<base::Mutex> lock(&mutex_);
abort_ = true; abort_ = true;
pending_background_jobs_.clear(); pending_background_jobs_.clear();
idle_task_scheduled_ = false; // Idle task cancelled by TryAbortAll.
} }
AbortInactiveJobs(); AbortInactiveJobs();
...@@ -421,9 +360,7 @@ void CompilerDispatcher::AbortInactiveJobs() { ...@@ -421,9 +360,7 @@ void CompilerDispatcher::AbortInactiveJobs() {
} }
} }
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
PrintF("CompilerDispatcher: aborted "); PrintF("CompilerDispatcher: aborted job %zu\n", job->first);
job->second->ShortPrintOnMainThread();
PrintF("\n");
} }
it = RemoveJob(job); it = RemoveJob(job);
} }
...@@ -470,8 +407,6 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( ...@@ -470,8 +407,6 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
JobMap::const_iterator job = jobs_.end(); JobMap::const_iterator job = jobs_.end();
if (job_id_ptr) { if (job_id_ptr) {
job = jobs_.find(*job_id_ptr); job = jobs_.find(*job_id_ptr);
DCHECK(job == jobs_.end() ||
job->second->AsUnoptimizedCompileJob()->IsAssociatedWith(shared));
} }
return job; return job;
} }
...@@ -481,7 +416,7 @@ void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() { ...@@ -481,7 +416,7 @@ void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() {
if (!platform_->IdleTasksEnabled(v8_isolate)) return; if (!platform_->IdleTasksEnabled(v8_isolate)) return;
{ {
base::LockGuard<base::Mutex> lock(&mutex_); base::LockGuard<base::Mutex> lock(&mutex_);
if (idle_task_scheduled_) return; if (idle_task_scheduled_ || abort_) return;
idle_task_scheduled_ = true; idle_task_scheduled_ = true;
} }
platform_->CallIdleOnForegroundThread( platform_->CallIdleOnForegroundThread(
...@@ -525,6 +460,8 @@ void CompilerDispatcher::ScheduleMoreWorkerTasksIfNeeded() { ...@@ -525,6 +460,8 @@ void CompilerDispatcher::ScheduleMoreWorkerTasksIfNeeded() {
} }
void CompilerDispatcher::DoBackgroundWork() { void CompilerDispatcher::DoBackgroundWork() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherDoBackgroundWork");
for (;;) { for (;;) {
CompilerDispatcherJob* job = nullptr; CompilerDispatcherJob* job = nullptr;
{ {
...@@ -547,7 +484,10 @@ void CompilerDispatcher::DoBackgroundWork() { ...@@ -547,7 +484,10 @@ void CompilerDispatcher::DoBackgroundWork() {
PrintF("CompilerDispatcher: doing background work\n"); PrintF("CompilerDispatcher: doing background work\n");
} }
DoNextStepOnBackgroundThread(job); DCHECK(job->NextStepCanRunOnAnyThread());
DCHECK_EQ(job->status(), CompilerDispatcherJob::Status::kInitial);
job->Compile(true);
// Unconditionally schedule an idle task, as all background steps have to be // Unconditionally schedule an idle task, as all background steps have to be
// followed by a main thread step. // followed by a main thread step.
ScheduleIdleTaskFromAnyThread(); ScheduleIdleTaskFromAnyThread();
...@@ -579,6 +519,8 @@ void CompilerDispatcher::DoBackgroundWork() { ...@@ -579,6 +519,8 @@ void CompilerDispatcher::DoBackgroundWork() {
} }
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.CompilerDispatcherDoIdleWork");
bool aborted = false; bool aborted = false;
{ {
base::LockGuard<base::Mutex> lock(&mutex_); base::LockGuard<base::Mutex> lock(&mutex_);
...@@ -593,11 +535,11 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { ...@@ -593,11 +535,11 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
// Number of jobs that are unlikely to make progress during any idle callback // Number of jobs that are unlikely to make progress during any idle callback
// due to their estimated duration. // due to their estimated duration.
size_t too_long_jobs = 0; size_t jobs_unlikely_to_progress = 0;
// Iterate over all available jobs & remaining time. For each job, decide // Iterate over all available jobs & remaining time. For each job, decide
// whether to 1) skip it (if it would take too long), 2) erase it (if it's // whether to 1) skip it (if it would take too long), 2) erase it (if it's
// finished), or 3) make progress on it. // finished), or 3) make progress on it if possible.
double idle_time_in_seconds = double idle_time_in_seconds =
deadline_in_seconds - platform_->MonotonicallyIncreasingTime(); deadline_in_seconds - platform_->MonotonicallyIncreasingTime();
...@@ -620,6 +562,7 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { ...@@ -620,6 +562,7 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
++job; ++job;
continue; continue;
} }
DCHECK(!job->second->IsFinished());
auto it = pending_background_jobs_.find(job->second.get()); auto it = pending_background_jobs_.find(job->second.get());
double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs(); double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs();
if (idle_time_in_seconds < if (idle_time_in_seconds <
...@@ -628,29 +571,44 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) { ...@@ -628,29 +571,44 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
// If there's not enough time left, try to estimate whether we would // If there's not enough time left, try to estimate whether we would
// have managed to finish the job in a large idle task to assess // have managed to finish the job in a large idle task to assess
// whether we should ask for another idle callback. // whether we should ask for another idle callback.
if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs; // TODO(rmcilroy): Consider running the job anyway when we have a long
// idle time since this would probably be the best time to run.
if (estimate_in_ms > kMaxIdleTimeToExpectInMs)
++jobs_unlikely_to_progress;
if (it == pending_background_jobs_.end()) { if (it == pending_background_jobs_.end()) {
lock.reset(); lock.reset();
ConsiderJobForBackgroundProcessing(job->second.get()); ConsiderJobForBackgroundProcessing(job->second.get());
} }
++job; ++job;
} else if (job->second->IsFinished()) { } else if (job->second->status() ==
DCHECK(it == pending_background_jobs_.end()); CompilerDispatcherJob::Status::kInitial) {
lock.reset();
job = RemoveJob(job);
continue;
} else {
// Do one step, and keep processing the job (as we don't advance the
// iterator).
if (it != pending_background_jobs_.end()) { if (it != pending_background_jobs_.end()) {
pending_background_jobs_.erase(it); pending_background_jobs_.erase(it);
} }
lock.reset(); lock.reset();
DoNextStepOnMainThread(isolate_, job->second.get(), job->second->Compile(false);
ExceptionHandling::kSwallow); // Don't update job so we can immediately finalize it on the next loop.
} else {
DCHECK_EQ(job->second->status(),
CompilerDispatcherJob::Status::kReadyToFinalize);
DCHECK(it == pending_background_jobs_.end());
lock.reset();
auto shared_it = job_id_to_shared_.find(job->first);
if (shared_it != job_id_to_shared_.end()) {
Handle<SharedFunctionInfo> shared = shared_it->second;
FinalizeJobOnMainThread(isolate_, job->second.get(), shared,
ExceptionHandling::kSwallow);
DCHECK(job->second->IsFinished());
job = RemoveJob(job);
} else {
// If we can't step the job yet, go to the next job.
++jobs_unlikely_to_progress;
++job;
}
} }
} }
if (jobs_.size() > too_long_jobs) ScheduleIdleTaskIfNeeded(); if (jobs_.size() > jobs_unlikely_to_progress) ScheduleIdleTaskIfNeeded();
} }
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveIfFinished( CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveIfFinished(
...@@ -661,9 +619,8 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveIfFinished( ...@@ -661,9 +619,8 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveIfFinished(
if (trace_compiler_dispatcher_) { if (trace_compiler_dispatcher_) {
bool result = !job->second->IsFailed(); bool result = !job->second->IsFailed();
PrintF("CompilerDispatcher: finished working on "); PrintF("CompilerDispatcher: finished working on job %zu: %s\n", job->first,
job->second->ShortPrintOnMainThread(); result ? "success" : "failure");
PrintF(": %s\n", result ? "success" : "failure");
tracer_->DumpStatistics(); tracer_->DumpStatistics();
} }
...@@ -677,44 +634,34 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::InsertJob( ...@@ -677,44 +634,34 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::InsertJob(
std::tie(it, added) = std::tie(it, added) =
jobs_.insert(std::make_pair(next_job_id_++, std::move(job))); jobs_.insert(std::make_pair(next_job_id_++, std::move(job)));
DCHECK(added); DCHECK(added);
JobId id = it->first;
CompilerDispatcherJob* inserted_job = it->second.get();
// Maps unoptimized jobs' SFIs to their job id.
if (inserted_job->type() ==
CompilerDispatcherJob::Type::kUnoptimizedCompile) {
Handle<SharedFunctionInfo> shared =
inserted_job->AsUnoptimizedCompileJob()->shared();
if (!shared.is_null()) {
shared_to_unoptimized_job_id_.Set(shared, id);
}
}
return it; return it;
} }
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveJob( CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::RemoveJob(
CompilerDispatcher::JobMap::const_iterator it) { CompilerDispatcher::JobMap::const_iterator it) {
CompilerDispatcherJob* job = it->second.get(); CompilerDispatcherJob* job = it->second.get();
job->ResetOnMainThread(isolate_);
// Unmaps unoptimized jobs' SFIs to their job id. // Delete SFI associated with job if its been registered.
if (job->type() == CompilerDispatcherJob::Type::kUnoptimizedCompile) { auto shared_it = job_id_to_shared_.find(it->first);
Handle<SharedFunctionInfo> shared = if (shared_it != job_id_to_shared_.end()) {
job->AsUnoptimizedCompileJob()->shared(); Handle<SharedFunctionInfo> shared = shared_it->second;
if (!shared.is_null()) {
JobId deleted_id; JobId deleted_id;
shared_to_unoptimized_job_id_.Delete(shared, &deleted_id); shared_to_unoptimized_job_id_.Delete(shared, &deleted_id);
DCHECK_EQ(it->first, deleted_id); DCHECK_EQ(it->first, deleted_id);
}
job_id_to_shared_.erase(shared_it);
GlobalHandles::Destroy(Handle<Object>::cast(shared).location());
} }
job->ResetOnMainThread(isolate_);
it = jobs_.erase(it); it = jobs_.erase(it);
if (jobs_.empty()) { if (jobs_.empty()) {
base::LockGuard<base::Mutex> lock(&mutex_); base::LockGuard<base::Mutex> lock(&mutex_);
if (num_worker_tasks_ == 0) abort_ = false; if (num_worker_tasks_ == 0) abort_ = false;
} }
return it; return it;
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/base/atomic-utils.h" #include "src/base/atomic-utils.h"
#include "src/base/macros.h" #include "src/base/macros.h"
#include "src/base/optional.h"
#include "src/base/platform/condition-variable.h" #include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h" #include "src/base/platform/mutex.h"
#include "src/base/platform/semaphore.h" #include "src/base/platform/semaphore.h"
...@@ -27,6 +28,7 @@ enum class MemoryPressureLevel; ...@@ -27,6 +28,7 @@ enum class MemoryPressureLevel;
namespace internal { namespace internal {
class AstRawString;
class AstValueFactory; class AstValueFactory;
class CancelableTaskManager; class CancelableTaskManager;
class CompilerDispatcherJob; class CompilerDispatcherJob;
...@@ -37,6 +39,7 @@ class FunctionLiteral; ...@@ -37,6 +39,7 @@ class FunctionLiteral;
class Isolate; class Isolate;
class ParseInfo; class ParseInfo;
class SharedFunctionInfo; class SharedFunctionInfo;
class WorkerThreadRuntimeCallStats;
class Zone; class Zone;
template <typename T> template <typename T>
...@@ -79,24 +82,23 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -79,24 +82,23 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
// Returns true if the compiler dispatcher is enabled. // Returns true if the compiler dispatcher is enabled.
bool IsEnabled() const; bool IsEnabled() const;
// Enqueue a job for parse and compile. Returns true if a job was enqueued. base::Optional<JobId> Enqueue(const ParseInfo* outer_parse_info,
bool Enqueue(Handle<SharedFunctionInfo> function); const AstRawString* function_name,
const FunctionLiteral* function_literal);
// Like Enqueue, but also advances the job so that it can potentially // Registers the given |function| with the compilation job |job_id|.
// continue running on a background thread (if at all possible). Returns void RegisterSharedFunctionInfo(JobId job_id, SharedFunctionInfo* function);
// true if the job was enqueued.
bool EnqueueAndStep(Handle<SharedFunctionInfo> function);
// Returns true if there is a pending job for the given function. // Returns true if there is a pending job with the given id.
bool IsEnqueued(JobId job_id) const;
// Returns true if there is a pending job registered for the given function.
bool IsEnqueued(Handle<SharedFunctionInfo> function) const; bool IsEnqueued(Handle<SharedFunctionInfo> function) const;
// Blocks until the given function is compiled (and does so as fast as // Blocks until the given function is compiled (and does so as fast as
// possible). Returns true if the compile job was successful. // possible). Returns true if the compile job was successful.
bool FinishNow(Handle<SharedFunctionInfo> function); bool FinishNow(Handle<SharedFunctionInfo> function);
// Blocks until all jobs are finished.
void FinishAllNow();
// Aborts a given job. Blocks if requested. // Aborts a given job. Blocks if requested.
void Abort(Handle<SharedFunctionInfo> function, BlockingBehavior blocking); void Abort(Handle<SharedFunctionInfo> function, BlockingBehavior blocking);
...@@ -124,15 +126,15 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -124,15 +126,15 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
FRIEND_TEST(CompilerDispatcherTest, CompileMultipleOnBackgroundThread); FRIEND_TEST(CompilerDispatcherTest, CompileMultipleOnBackgroundThread);
typedef std::map<JobId, std::unique_ptr<CompilerDispatcherJob>> JobMap; typedef std::map<JobId, std::unique_ptr<CompilerDispatcherJob>> JobMap;
typedef std::map<JobId, Handle<SharedFunctionInfo>> JobIdToSharedMap;
typedef IdentityMap<JobId, FreeStoreAllocationPolicy> SharedToJobIdMap; typedef IdentityMap<JobId, FreeStoreAllocationPolicy> SharedToJobIdMap;
class AbortTask; class AbortTask;
class WorkerTask; class WorkerTask;
class IdleTask; class IdleTask;
bool CanEnqueue();
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job); void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
void AbortInactiveJobs(); void AbortInactiveJobs();
bool CanEnqueue();
bool CanEnqueue(Handle<SharedFunctionInfo> function);
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const; JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job); void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
void ScheduleMoreWorkerTasksIfNeeded(); void ScheduleMoreWorkerTasksIfNeeded();
...@@ -141,17 +143,16 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -141,17 +143,16 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
void ScheduleAbortTask(); void ScheduleAbortTask();
void DoBackgroundWork(); void DoBackgroundWork();
void DoIdleWork(double deadline_in_seconds); void DoIdleWork(double deadline_in_seconds);
JobId Enqueue(std::unique_ptr<CompilerDispatcherJob> job);
JobId EnqueueAndStep(std::unique_ptr<CompilerDispatcherJob> job);
// Returns job if not removed otherwise iterator following the removed job. // Returns job if not removed otherwise iterator following the removed job.
JobMap::const_iterator RemoveIfFinished(JobMap::const_iterator job); JobMap::const_iterator RemoveIfFinished(JobMap::const_iterator job);
// Returns iterator to the inserted job. // Returns iterator to the inserted job.
JobMap::const_iterator InsertJob(std::unique_ptr<CompilerDispatcherJob> job); JobMap::const_iterator InsertJob(std::unique_ptr<CompilerDispatcherJob> job);
// Returns iterator following the removed job. // Returns iterator following the removed job.
JobMap::const_iterator RemoveJob(JobMap::const_iterator job); JobMap::const_iterator RemoveJob(JobMap::const_iterator job);
bool FinishNow(CompilerDispatcherJob* job);
Isolate* isolate_; Isolate* isolate_;
AccountingAllocator* allocator_;
WorkerThreadRuntimeCallStats* worker_thread_runtime_call_stats_;
Platform* platform_; Platform* platform_;
size_t max_stack_size_; size_t max_stack_size_;
...@@ -168,6 +169,9 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -168,6 +169,9 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
// Mapping from job_id to job. // Mapping from job_id to job.
JobMap jobs_; JobMap jobs_;
// Mapping from job_id to SharedFunctionInfo.
JobIdToSharedMap job_id_to_shared_;
// Mapping from SharedFunctionInfo to the corresponding unoptimized // Mapping from SharedFunctionInfo to the corresponding unoptimized
// compilation's JobId; // compilation's JobId;
SharedToJobIdMap shared_to_unoptimized_job_id_; SharedToJobIdMap shared_to_unoptimized_job_id_;
......
...@@ -5,11 +5,9 @@ ...@@ -5,11 +5,9 @@
#include "src/compiler-dispatcher/unoptimized-compile-job.h" #include "src/compiler-dispatcher/unoptimized-compile-job.h"
#include "src/assert-scope.h" #include "src/assert-scope.h"
#include "src/base/optional.h"
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" #include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
#include "src/compiler.h" #include "src/compiler.h"
#include "src/flags.h" #include "src/flags.h"
#include "src/global-handles.h"
#include "src/interpreter/interpreter.h" #include "src/interpreter/interpreter.h"
#include "src/isolate.h" #include "src/isolate.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
...@@ -25,227 +23,135 @@ namespace internal { ...@@ -25,227 +23,135 @@ namespace internal {
namespace { namespace {
class OneByteWrapper : public v8::String::ExternalOneByteStringResource { // A scope object that ensures a parse info's runtime call stats field is set
// correctly during worker-thread compile, and restores it after going out of
// scope.
class OffThreadRuntimeCallStatsScope {
public: public:
OneByteWrapper(const void* data, int length) : data_(data), length_(length) {} OffThreadRuntimeCallStatsScope(
~OneByteWrapper() override = default; ParseInfo* parse_info,
WorkerThreadRuntimeCallStats* worker_thread_runtime_stats)
const char* data() const override { : parse_info_(parse_info),
return reinterpret_cast<const char*>(data_); original_runtime_call_stats_(parse_info_->runtime_call_stats()),
worker_thread_scope_(worker_thread_runtime_stats) {
parse_info_->set_runtime_call_stats(worker_thread_scope_.Get());
} }
size_t length() const override { return static_cast<size_t>(length_); } ~OffThreadRuntimeCallStatsScope() {
parse_info_->set_runtime_call_stats(original_runtime_call_stats_);
private:
const void* data_;
int length_;
DISALLOW_COPY_AND_ASSIGN(OneByteWrapper);
};
class TwoByteWrapper : public v8::String::ExternalStringResource {
public:
TwoByteWrapper(const void* data, int length) : data_(data), length_(length) {}
~TwoByteWrapper() override = default;
const uint16_t* data() const override {
return reinterpret_cast<const uint16_t*>(data_);
} }
size_t length() const override { return static_cast<size_t>(length_); }
private: private:
const void* data_; ParseInfo* parse_info_;
int length_; RuntimeCallStats* original_runtime_call_stats_;
WorkerThreadRuntimeCallStatsScope worker_thread_scope_;
DISALLOW_COPY_AND_ASSIGN(TwoByteWrapper);
}; };
} // namespace } // namespace
UnoptimizedCompileJob::UnoptimizedCompileJob(Isolate* isolate, UnoptimizedCompileJob::UnoptimizedCompileJob(
CompilerDispatcherTracer* tracer, CompilerDispatcherTracer* tracer, AccountingAllocator* allocator,
Handle<SharedFunctionInfo> shared, const ParseInfo* outer_parse_info, const AstRawString* function_name,
size_t max_stack_size) const FunctionLiteral* function_literal,
WorkerThreadRuntimeCallStats* worker_thread_runtime_stats,
size_t max_stack_size)
: CompilerDispatcherJob(Type::kUnoptimizedCompile), : CompilerDispatcherJob(Type::kUnoptimizedCompile),
main_thread_id_(isolate->thread_id().ToInteger()),
tracer_(tracer), tracer_(tracer),
allocator_(isolate->allocator()), allocator_(allocator),
context_(isolate->global_handles()->Create(isolate->context())), worker_thread_runtime_stats_(worker_thread_runtime_stats),
shared_(isolate->global_handles()->Create(*shared)),
worker_thread_runtime_stats_(
isolate->counters()->worker_thread_runtime_call_stats()),
max_stack_size_(max_stack_size), max_stack_size_(max_stack_size),
trace_compiler_dispatcher_jobs_(FLAG_trace_compiler_dispatcher_jobs) { trace_compiler_dispatcher_jobs_(FLAG_trace_compiler_dispatcher_jobs) {
DCHECK(!shared_->is_toplevel()); DCHECK(outer_parse_info->is_toplevel());
// TODO(rmcilroy): Handle functions with non-empty outer scope info. DCHECK(!function_literal->is_toplevel());
DCHECK(!shared_->HasOuterScopeInfo());
HandleScope scope(isolate); // Initialize parse_info for the given function details.
Handle<Script> script(Script::cast(shared_->script()), isolate); parse_info_ = ParseInfo::FromParent(outer_parse_info, allocator_,
Handle<String> source(String::cast(script->source()), isolate); function_literal, function_name);
if (trace_compiler_dispatcher_jobs_) {
PrintF("UnoptimizedCompileJob[%p] created for ", static_cast<void*>(this)); // Clone the character stream so both can be accessed independently.
ShortPrintOnMainThread(); std::unique_ptr<Utf16CharacterStream> character_stream =
PrintF(" in initial state.\n"); outer_parse_info->character_stream()->Clone();
} character_stream->Seek(function_literal->start_position());
} parse_info_->set_character_stream(std::move(character_stream));
UnoptimizedCompileJob::~UnoptimizedCompileJob() { // Get preparsed scope data from the function literal.
DCHECK(status() == Status::kInitial || status() == Status::kDone); if (function_literal->produced_preparsed_scope_data()) {
if (!shared_.is_null()) { DCHECK(FLAG_preparser_scope_analysis);
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); ZonePreParsedScopeData* serialized_data =
i::GlobalHandles::Destroy(Handle<Object>::cast(shared_).location()); function_literal->produced_preparsed_scope_data()->Serialize(
} parse_info_->zone());
if (!context_.is_null()) { parse_info_->set_consumed_preparsed_scope_data(
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); ConsumedPreParsedScopeData::For(parse_info_->zone(), serialized_data));
i::GlobalHandles::Destroy(Handle<Object>::cast(context_).location());
}
}
bool UnoptimizedCompileJob::IsAssociatedWith(
Handle<SharedFunctionInfo> shared) const {
return *shared_ == *shared;
}
void UnoptimizedCompileJob::PrepareOnMainThread(Isolate* isolate) {
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_);
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_);
DCHECK_EQ(status(), Status::kInitial);
COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kPrepare);
if (trace_compiler_dispatcher_jobs_) {
PrintF("UnoptimizedCompileJob[%p]: Preparing to parse\n",
static_cast<void*>(this));
} }
ParseInfo* parse_info = new ParseInfo(isolate, shared_); // Create a unicode cache for the parse-info.
parse_info_.reset(parse_info); // TODO(rmcilroy): Try to reuse an existing one.
unicode_cache_.reset(new UnicodeCache()); unicode_cache_.reset(new UnicodeCache());
parse_info_->set_unicode_cache(unicode_cache_.get()); parse_info_->set_unicode_cache(unicode_cache_.get());
parse_info_->set_function_literal_id(shared_->FunctionLiteralId(isolate));
Handle<Script> script = parse_info->script(); parser_.reset(new Parser(parse_info_.get()));
HandleScope scope(isolate);
DCHECK(script->type() != Script::TYPE_NATIVE); if (trace_compiler_dispatcher_jobs_) {
Handle<String> source(String::cast(script->source()), isolate); PrintF(
if (source->IsExternalTwoByteString() || source->IsExternalOneByteString()) { "UnoptimizedCompileJob[%p] created for function literal id %d in "
std::unique_ptr<Utf16CharacterStream> stream(ScannerStream::For( "initial state.\n",
isolate, source, shared_->StartPosition(), shared_->EndPosition())); static_cast<void*>(this), function_literal->function_literal_id());
parse_info_->set_character_stream(std::move(stream));
} else {
source = String::Flatten(isolate, source);
const void* data;
int offset = 0;
int length = source->length();
// Objects in lo_space don't move, so we can just read the contents from
// any thread.
if (isolate->heap()->lo_space()->Contains(*source)) {
// We need to globalize the handle to the flattened string here, in
// case it's not referenced from anywhere else.
source_ = isolate->global_handles()->Create(*source);
DisallowHeapAllocation no_allocation;
String::FlatContent content = source->GetFlatContent();
DCHECK(content.IsFlat());
data =
content.IsOneByte()
? reinterpret_cast<const void*>(content.ToOneByteVector().start())
: reinterpret_cast<const void*>(content.ToUC16Vector().start());
} else {
// Otherwise, create a copy of the part of the string we'll parse in the
// zone.
length = (shared_->EndPosition() - shared_->StartPosition());
offset = shared_->StartPosition();
int byte_len = length * (source->IsOneByteRepresentation() ? 1 : 2);
data = parse_info_->zone()->New(byte_len);
DisallowHeapAllocation no_allocation;
String::FlatContent content = source->GetFlatContent();
DCHECK(content.IsFlat());
if (content.IsOneByte()) {
MemCopy(const_cast<void*>(data),
&content.ToOneByteVector().at(shared_->StartPosition()),
byte_len);
} else {
MemCopy(const_cast<void*>(data),
&content.ToUC16Vector().at(shared_->StartPosition()), byte_len);
}
}
Handle<String> wrapper;
if (source->IsOneByteRepresentation()) {
ExternalOneByteString::Resource* resource =
new OneByteWrapper(data, length);
wrapper = isolate->factory()
->NewExternalStringFromOneByte(resource)
.ToHandleChecked();
} else {
ExternalTwoByteString::Resource* resource =
new TwoByteWrapper(data, length);
wrapper = isolate->factory()
->NewExternalStringFromTwoByte(resource)
.ToHandleChecked();
}
wrapper_ = isolate->global_handles()->Create(*wrapper);
std::unique_ptr<Utf16CharacterStream> stream(
ScannerStream::For(isolate, wrapper_, shared_->StartPosition() - offset,
shared_->EndPosition() - offset));
parse_info_->set_character_stream(std::move(stream));
// Set script to null in parse_info so that it's not dereferenced on the
// background thread.
} }
}
// Initailize the name after setting up the ast_value_factory. UnoptimizedCompileJob::~UnoptimizedCompileJob() {
Handle<String> name(shared_->Name(), isolate); DCHECK(status() == Status::kInitial || status() == Status::kDone);
parse_info_->set_function_name(
parse_info_->GetOrCreateAstValueFactory()->GetString(name));
// Clear the parse info's script handle to ensure it's not dereferenced
// on the background thread.
parse_info->ClearScriptHandle();
set_status(Status::kPrepared);
} }
void UnoptimizedCompileJob::Compile(bool on_background_thread) { void UnoptimizedCompileJob::Compile(bool on_background_thread) {
DCHECK_EQ(status(), Status::kPrepared); DCHECK_EQ(status(), Status::kInitial);
COMPILER_DISPATCHER_TRACE_SCOPE_WITH_NUM( COMPILER_DISPATCHER_TRACE_SCOPE_WITH_NUM(
tracer_, kCompile, tracer_, kCompile,
parse_info_->end_position() - parse_info_->start_position()); parse_info_->end_position() - parse_info_->start_position());
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.UnoptimizedCompileJob::Compile");
if (trace_compiler_dispatcher_jobs_) { if (trace_compiler_dispatcher_jobs_) {
PrintF("UnoptimizedCompileJob[%p]: Compiling\n", static_cast<void*>(this)); PrintF("UnoptimizedCompileJob[%p]: Compiling\n", static_cast<void*>(this));
} }
DisallowHeapAllocation no_allocation; DisallowHeapAllocation no_allocation;
DisallowHandleAllocation no_handles; DisallowHandleAllocation no_handles;
DisallowHandleDereference no_deref; DisallowHandleDereference no_deref;
parse_info_->set_on_background_thread(on_background_thread); base::Optional<OffThreadRuntimeCallStatsScope> runtime_call_stats_scope;
base::Optional<WorkerThreadRuntimeCallStatsScope> runtime_call_stats_scope;
if (V8_UNLIKELY(FLAG_runtime_stats && on_background_thread)) { if (V8_UNLIKELY(FLAG_runtime_stats && on_background_thread)) {
runtime_call_stats_scope.emplace(worker_thread_runtime_stats_); runtime_call_stats_scope.emplace(parse_info_.get(),
parse_info_->set_runtime_call_stats(runtime_call_stats_scope->Get()); worker_thread_runtime_stats_);
} }
RuntimeCallTimerScope runtimeTimer(
parse_info_->runtime_call_stats(),
on_background_thread
? RuntimeCallCounterId::kCompileBackgroundUnoptimizedCompileJob
: RuntimeCallCounterId::kCompileUnoptimizedCompileJob);
parse_info_->set_on_background_thread(on_background_thread);
uintptr_t stack_limit = GetCurrentStackPosition() - max_stack_size_ * KB; uintptr_t stack_limit = GetCurrentStackPosition() - max_stack_size_ * KB;
parse_info_->set_stack_limit(stack_limit); parse_info_->set_stack_limit(stack_limit);
parser_.reset(new Parser(parse_info_.get())); parser_.reset(new Parser(parse_info_.get()));
parser_->set_stack_limit(stack_limit); parser_->set_stack_limit(stack_limit);
// We only support compilation of functions with no outer scope info
// therefore it is correct to use an empty scope chain.
DCHECK(parse_info_->maybe_outer_scope_info().is_null());
parser_->InitializeEmptyScopeChain(parse_info_.get()); parser_->InitializeEmptyScopeChain(parse_info_.get());
parser_->ParseOnBackground(parse_info_.get()); parser_->ParseOnBackground(parse_info_.get());
if (parse_info_->literal() == nullptr) { if (parse_info_->literal() == nullptr) {
// Parser sets error in pending error handler. // Parser sets error in pending error handler.
set_status(Status::kHasErrorsToReport); set_status(Status::kReadyToFinalize);
return; return;
} }
if (!Compiler::Analyze(parse_info_.get())) { if (!Compiler::Analyze(parse_info_.get())) {
parse_info_->pending_error_handler()->set_stack_overflow(); parse_info_->pending_error_handler()->set_stack_overflow();
set_status(Status::kHasErrorsToReport); set_status(Status::kReadyToFinalize);
return; return;
} }
...@@ -254,99 +160,75 @@ void UnoptimizedCompileJob::Compile(bool on_background_thread) { ...@@ -254,99 +160,75 @@ void UnoptimizedCompileJob::Compile(bool on_background_thread) {
if (!compilation_job_.get()) { if (!compilation_job_.get()) {
parse_info_->pending_error_handler()->set_stack_overflow(); parse_info_->pending_error_handler()->set_stack_overflow();
set_status(Status::kHasErrorsToReport); set_status(Status::kReadyToFinalize);
return; return;
} }
if (compilation_job_->ExecuteJob() != CompilationJob::SUCCEEDED) { if (compilation_job_->ExecuteJob() != CompilationJob::SUCCEEDED) {
parse_info_->pending_error_handler()->set_stack_overflow(); parse_info_->pending_error_handler()->set_stack_overflow();
set_status(Status::kHasErrorsToReport); set_status(Status::kReadyToFinalize);
return; return;
} }
set_status(Status::kCompiled); set_status(Status::kReadyToFinalize);
} }
void UnoptimizedCompileJob::FinalizeOnMainThread(Isolate* isolate) { void UnoptimizedCompileJob::FinalizeOnMainThread(
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); Isolate* isolate, Handle<SharedFunctionInfo> shared) {
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); DCHECK_EQ(ThreadId::Current().ToInteger(), isolate->thread_id().ToInteger());
DCHECK_EQ(status(), Status::kCompiled); DCHECK_EQ(status(), Status::kReadyToFinalize);
DCHECK_NOT_NULL(parse_info_->literal());
DCHECK_NOT_NULL(compilation_job_.get());
COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kFinalize); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kFinalize);
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.UnoptimizedCompileJob::FinalizeOnMainThread");
RuntimeCallTimerScope runtimeTimer(
isolate, RuntimeCallCounterId::kCompileFinalizeUnoptimizedCompileJob);
if (trace_compiler_dispatcher_jobs_) { if (trace_compiler_dispatcher_jobs_) {
PrintF("UnoptimizedCompileJob[%p]: Finalizing compiling\n", PrintF("UnoptimizedCompileJob[%p]: Finalizing compiling\n",
static_cast<void*>(this)); static_cast<void*>(this));
} }
Handle<Script> script(Script::cast(shared_->script()), isolate); HandleScope scope(isolate);
Handle<Script> script(Script::cast(shared->script()), isolate);
parse_info_->set_script(script); parse_info_->set_script(script);
parser_->UpdateStatistics(isolate, script); parser_->UpdateStatistics(isolate, script);
parser_->HandleSourceURLComments(isolate, script); parser_->HandleSourceURLComments(isolate, script);
{ if (!parse_info_->literal() || !compilation_job_.get()) {
HandleScope scope(isolate); // Parse or compile failed on the main thread, report errors.
// Internalize ast values onto the heap. parse_info_->pending_error_handler()->ReportErrors(
parse_info_->ast_value_factory()->Internalize(isolate); isolate, script, parse_info_->ast_value_factory());
// Allocate scope infos for the literal. ResetDataOnMainThread(isolate);
DeclarationScope::AllocateScopeInfos(parse_info_.get(), isolate); set_status(Status::kFailed);
if (compilation_job_->state() == CompilationJob::State::kFailed || return;
!Compiler::FinalizeCompilationJob(compilation_job_.release(), shared_,
isolate)) {
if (!isolate->has_pending_exception()) isolate->StackOverflow();
set_status(Status::kFailed);
return;
}
} }
ResetDataOnMainThread(isolate); // Internalize ast values onto the heap.
set_status(Status::kDone); parse_info_->ast_value_factory()->Internalize(isolate);
} // Allocate scope infos for the literal.
DeclarationScope::AllocateScopeInfos(parse_info_.get(), isolate);
void UnoptimizedCompileJob::ReportErrorsOnMainThread(Isolate* isolate) { if (compilation_job_->state() == CompilationJob::State::kFailed ||
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); !Compiler::FinalizeCompilationJob(compilation_job_.release(), shared,
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); isolate)) {
DCHECK_EQ(status(), Status::kHasErrorsToReport); if (!isolate->has_pending_exception()) isolate->StackOverflow();
ResetDataOnMainThread(isolate);
if (trace_compiler_dispatcher_jobs_) { set_status(Status::kFailed);
PrintF("UnoptimizedCompileJob[%p]: Reporting Errors\n", return;
static_cast<void*>(this));
} }
// Ensure we report errors in the correct context for the job.
SaveContext save(isolate);
isolate->set_context(context());
Handle<Script> script(Script::cast(shared_->script()), isolate);
parse_info_->pending_error_handler()->ReportErrors(
isolate, script, parse_info_->ast_value_factory());
ResetDataOnMainThread(isolate); ResetDataOnMainThread(isolate);
set_status(Status::kFailed); set_status(Status::kDone);
} }
void UnoptimizedCompileJob::ResetDataOnMainThread(Isolate* isolate) { void UnoptimizedCompileJob::ResetDataOnMainThread(Isolate* isolate) {
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); DCHECK_EQ(ThreadId::Current().ToInteger(), isolate->thread_id().ToInteger());
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.compile"),
"V8.UnoptimizedCompileJob::ResetDataOnMainThread");
compilation_job_.reset(); compilation_job_.reset();
parser_.reset(); parser_.reset();
unicode_cache_.reset(); unicode_cache_.reset();
parse_info_.reset(); parse_info_.reset();
if (!source_.is_null()) {
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_);
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_);
i::GlobalHandles::Destroy(Handle<Object>::cast(source_).location());
source_ = Handle<String>::null();
}
if (!wrapper_.is_null()) {
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_);
DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_);
i::GlobalHandles::Destroy(Handle<Object>::cast(wrapper_).location());
wrapper_ = Handle<String>::null();
}
} }
void UnoptimizedCompileJob::ResetOnMainThread(Isolate* isolate) { void UnoptimizedCompileJob::ResetOnMainThread(Isolate* isolate) {
...@@ -361,14 +243,12 @@ void UnoptimizedCompileJob::ResetOnMainThread(Isolate* isolate) { ...@@ -361,14 +243,12 @@ void UnoptimizedCompileJob::ResetOnMainThread(Isolate* isolate) {
double UnoptimizedCompileJob::EstimateRuntimeOfNextStepInMs() const { double UnoptimizedCompileJob::EstimateRuntimeOfNextStepInMs() const {
switch (status()) { switch (status()) {
case Status::kInitial: case Status::kInitial:
return tracer_->EstimatePrepareInMs();
case Status::kPrepared:
return tracer_->EstimateCompileInMs(parse_info_->end_position() - return tracer_->EstimateCompileInMs(parse_info_->end_position() -
parse_info_->start_position()); parse_info_->start_position());
case Status::kCompiled: case Status::kReadyToFinalize:
// TODO(rmcilroy): Pass size of bytecode to tracer to get better estimate.
return tracer_->EstimateFinalizeInMs(); return tracer_->EstimateFinalizeInMs();
case Status::kHasErrorsToReport:
case Status::kFailed: case Status::kFailed:
case Status::kDone: case Status::kDone:
return 0.0; return 0.0;
...@@ -377,11 +257,5 @@ double UnoptimizedCompileJob::EstimateRuntimeOfNextStepInMs() const { ...@@ -377,11 +257,5 @@ double UnoptimizedCompileJob::EstimateRuntimeOfNextStepInMs() const {
UNREACHABLE(); UNREACHABLE();
} }
void UnoptimizedCompileJob::ShortPrintOnMainThread() {
DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_);
DCHECK(!shared_.is_null());
shared_->ShortPrint();
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
class AccountingAllocator;
class AstRawString;
class AstValueFactory; class AstValueFactory;
class AstStringConstants; class AstStringConstants;
class CompilerDispatcherTracer; class CompilerDispatcherTracer;
...@@ -27,31 +29,25 @@ class SharedFunctionInfo; ...@@ -27,31 +29,25 @@ class SharedFunctionInfo;
class String; class String;
class UnicodeCache; class UnicodeCache;
class UnoptimizedCompilationJob; class UnoptimizedCompilationJob;
class Utf16CharacterStream;
class WorkerThreadRuntimeCallStats; class WorkerThreadRuntimeCallStats;
class V8_EXPORT_PRIVATE UnoptimizedCompileJob : public CompilerDispatcherJob { class V8_EXPORT_PRIVATE UnoptimizedCompileJob : public CompilerDispatcherJob {
public: public:
// Creates a UnoptimizedCompileJob in the initial state. // Creates a UnoptimizedCompileJob in the initial state.
UnoptimizedCompileJob(Isolate* isolate, CompilerDispatcherTracer* tracer, UnoptimizedCompileJob(
Handle<SharedFunctionInfo> shared, CompilerDispatcherTracer* tracer, AccountingAllocator* allocator,
size_t max_stack_size); const ParseInfo* outer_parse_info, const AstRawString* function_name,
const FunctionLiteral* function_literal,
WorkerThreadRuntimeCallStats* worker_thread_runtime_stats,
size_t max_stack_size);
~UnoptimizedCompileJob() override; ~UnoptimizedCompileJob() override;
Handle<SharedFunctionInfo> shared() const { return shared_; }
// Returns true if this UnoptimizedCompileJob was created for the given
// function.
bool IsAssociatedWith(Handle<SharedFunctionInfo> shared) const;
// CompilerDispatcherJob implementation. // CompilerDispatcherJob implementation.
void PrepareOnMainThread(Isolate* isolate) override;
void Compile(bool on_background_thread) override; void Compile(bool on_background_thread) override;
void FinalizeOnMainThread(Isolate* isolate) override; void FinalizeOnMainThread(Isolate* isolate,
void ReportErrorsOnMainThread(Isolate* isolate) override; Handle<SharedFunctionInfo> shared) override;
void ResetOnMainThread(Isolate* isolate) override; void ResetOnMainThread(Isolate* isolate) override;
double EstimateRuntimeOfNextStepInMs() const override; double EstimateRuntimeOfNextStepInMs() const override;
void ShortPrintOnMainThread() override;
private: private:
friend class CompilerDispatcherTest; friend class CompilerDispatcherTest;
...@@ -59,15 +55,8 @@ class V8_EXPORT_PRIVATE UnoptimizedCompileJob : public CompilerDispatcherJob { ...@@ -59,15 +55,8 @@ class V8_EXPORT_PRIVATE UnoptimizedCompileJob : public CompilerDispatcherJob {
void ResetDataOnMainThread(Isolate* isolate); void ResetDataOnMainThread(Isolate* isolate);
Context* context() { return *context_; }
int main_thread_id_;
CompilerDispatcherTracer* tracer_; CompilerDispatcherTracer* tracer_;
AccountingAllocator* allocator_; AccountingAllocator* allocator_;
Handle<Context> context_; // Global handle.
Handle<SharedFunctionInfo> shared_; // Global handle.
Handle<String> source_; // Global handle.
Handle<String> wrapper_; // Global handle.
WorkerThreadRuntimeCallStats* worker_thread_runtime_stats_; WorkerThreadRuntimeCallStats* worker_thread_runtime_stats_;
size_t max_stack_size_; size_t max_stack_size_;
......
...@@ -872,9 +872,13 @@ class RuntimeCallTimer final { ...@@ -872,9 +872,13 @@ class RuntimeCallTimer final {
V(CompileBackgroundScript) \ V(CompileBackgroundScript) \
V(CompileBackgroundRewriteReturnResult) \ V(CompileBackgroundRewriteReturnResult) \
V(CompileBackgroundScopeAnalysis) \ V(CompileBackgroundScopeAnalysis) \
V(CompileBackgroundUnoptimizedCompileJob) \
V(CompileDeserialize) \ V(CompileDeserialize) \
V(CompileEval) \ V(CompileEval) \
V(CompileAnalyse) \ V(CompileAnalyse) \
V(CompileEnqueueOnDispatcher) \
V(CompileFinalizeUnoptimizedCompileJob) \
V(CompileFinishNowOnDispatcher) \
V(CompileFunction) \ V(CompileFunction) \
V(CompileGetFromOptimizedCodeMap) \ V(CompileGetFromOptimizedCodeMap) \
V(CompileIgnition) \ V(CompileIgnition) \
...@@ -883,6 +887,7 @@ class RuntimeCallTimer final { ...@@ -883,6 +887,7 @@ class RuntimeCallTimer final {
V(CompileScopeAnalysis) \ V(CompileScopeAnalysis) \
V(CompileScript) \ V(CompileScript) \
V(CompileSerialize) \ V(CompileSerialize) \
V(CompileUnoptimizedCompileJob) \
V(CompileWaitForDispatcher) \ V(CompileWaitForDispatcher) \
V(DeoptimizeCode) \ V(DeoptimizeCode) \
V(FunctionCallback) \ V(FunctionCallback) \
......
...@@ -1820,11 +1820,11 @@ bool BytecodeGenerator::ShouldOptimizeAsOneShot() const { ...@@ -1820,11 +1820,11 @@ bool BytecodeGenerator::ShouldOptimizeAsOneShot() const {
if (loop_depth_ > 0) return false; if (loop_depth_ > 0) return false;
// non-top-level iife is likely to be executed multiple times and so // A non-top-level iife is likely to be executed multiple times and so
// shouldn`t be optimized as one-shot // shouldn`t be optimized as one-shot.
bool is_top_level_iife = info()->literal()->is_iife() && bool is_toplevel_iife = info()->literal()->is_iife() &&
current_scope()->outer_scope()->is_script_scope(); current_scope()->outer_scope()->is_script_scope();
return info()->literal()->is_top_level() || is_top_level_iife; return info()->literal()->is_toplevel() || is_toplevel_iife;
} }
void BytecodeGenerator::BuildClassLiteral(ClassLiteral* expr) { void BytecodeGenerator::BuildClassLiteral(ClassLiteral* expr) {
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
namespace v8 { namespace v8 {
namespace internal { namespace internal {
ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator) ParseInfo::ParseInfo(AccountingAllocator* zone_allocator)
: zone_(base::make_unique<Zone>(zone_allocator, ZONE_NAME)), : zone_(base::make_unique<Zone>(zone_allocator, ZONE_NAME)),
flags_(0), flags_(0),
extension_(nullptr), extension_(nullptr),
...@@ -37,7 +37,10 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator) ...@@ -37,7 +37,10 @@ ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator)
function_name_(nullptr), function_name_(nullptr),
runtime_call_stats_(nullptr), runtime_call_stats_(nullptr),
source_range_map_(nullptr), source_range_map_(nullptr),
literal_(nullptr) { literal_(nullptr) {}
ParseInfo::ParseInfo(Isolate* isolate, AccountingAllocator* zone_allocator)
: ParseInfo(zone_allocator) {
set_hash_seed(isolate->heap()->HashSeed()); set_hash_seed(isolate->heap()->HashSeed());
set_stack_limit(isolate->stack_guard()->real_climit()); set_stack_limit(isolate->stack_guard()->real_climit());
set_unicode_cache(isolate->unicode_cache()); set_unicode_cache(isolate->unicode_cache());
...@@ -54,6 +57,18 @@ ParseInfo::ParseInfo(Isolate* isolate) ...@@ -54,6 +57,18 @@ ParseInfo::ParseInfo(Isolate* isolate)
LOG(isolate, ScriptEvent(Logger::ScriptEventType::kReserveId, script_id_)); LOG(isolate, ScriptEvent(Logger::ScriptEventType::kReserveId, script_id_));
} }
template <typename T>
void ParseInfo::SetFunctionInfo(T function) {
set_is_named_expression(function->is_named_expression());
set_language_mode(function->language_mode());
set_function_kind(function->kind());
set_declaration(function->is_declaration());
set_requires_instance_fields_initializer(
function->requires_instance_fields_initializer());
set_toplevel(function->is_toplevel());
set_wrapped_as_function(function->is_wrapped());
}
ParseInfo::ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared) ParseInfo::ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared)
: ParseInfo(isolate, isolate->allocator()) { : ParseInfo(isolate, isolate->allocator()) {
// Do not support re-parsing top-level function of a wrapped script. // Do not support re-parsing top-level function of a wrapped script.
...@@ -61,19 +76,13 @@ ParseInfo::ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared) ...@@ -61,19 +76,13 @@ ParseInfo::ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared)
// wrapped script at all. // wrapped script at all.
DCHECK_IMPLIES(is_toplevel(), !Script::cast(shared->script())->is_wrapped()); DCHECK_IMPLIES(is_toplevel(), !Script::cast(shared->script())->is_wrapped());
set_toplevel(shared->is_toplevel());
set_wrapped_as_function(shared->is_wrapped());
set_allow_lazy_parsing(FLAG_lazy_inner_functions); set_allow_lazy_parsing(FLAG_lazy_inner_functions);
set_is_named_expression(shared->is_named_expression()); set_asm_wasm_broken(shared->is_asm_wasm_broken());
set_start_position(shared->StartPosition()); set_start_position(shared->StartPosition());
set_end_position(shared->EndPosition()); set_end_position(shared->EndPosition());
function_literal_id_ = shared->FunctionLiteralId(isolate); function_literal_id_ = shared->FunctionLiteralId(isolate);
set_language_mode(shared->language_mode()); SetFunctionInfo(shared);
set_function_kind(shared->kind());
set_declaration(shared->is_declaration());
set_requires_instance_fields_initializer(
shared->requires_instance_fields_initializer());
set_asm_wasm_broken(shared->is_asm_wasm_broken());
Handle<Script> script(Script::cast(shared->script()), isolate); Handle<Script> script(Script::cast(shared->script()), isolate);
set_script(script); set_script(script);
...@@ -99,6 +108,41 @@ ParseInfo::ParseInfo(Isolate* isolate, Handle<Script> script) ...@@ -99,6 +108,41 @@ ParseInfo::ParseInfo(Isolate* isolate, Handle<Script> script)
script->IsUserJavaScript()); script->IsUserJavaScript());
} }
// static
std::unique_ptr<ParseInfo> ParseInfo::FromParent(
const ParseInfo* outer_parse_info, AccountingAllocator* zone_allocator,
const FunctionLiteral* literal, const AstRawString* function_name) {
std::unique_ptr<ParseInfo> result =
base::make_unique<ParseInfo>(zone_allocator);
// Replicate shared state of the outer_parse_info.
result->flags_ = outer_parse_info->flags_;
result->script_id_ = outer_parse_info->script_id_;
result->set_logger(outer_parse_info->logger());
result->set_ast_string_constants(outer_parse_info->ast_string_constants());
result->set_hash_seed(outer_parse_info->hash_seed());
DCHECK_EQ(outer_parse_info->parameters_end_pos(), kNoSourcePosition);
DCHECK_NULL(outer_parse_info->extension());
DCHECK(outer_parse_info->maybe_outer_scope_info().is_null());
// Clone the function_name AstRawString into the ParseInfo's own
// AstValueFactory.
const AstRawString* cloned_function_name =
result->GetOrCreateAstValueFactory()->CloneFromOtherFactory(
function_name);
// Setup function specific details.
DCHECK(!literal->is_toplevel());
result->set_function_name(cloned_function_name);
result->set_start_position(literal->start_position());
result->set_end_position(literal->end_position());
result->set_function_literal_id(literal->function_literal_id());
result->SetFunctionInfo(literal);
return result;
}
ParseInfo::~ParseInfo() = default; ParseInfo::~ParseInfo() = default;
DeclarationScope* ParseInfo::scope() const { return literal()->scope(); } DeclarationScope* ParseInfo::scope() const { return literal()->scope(); }
......
...@@ -38,11 +38,18 @@ class Zone; ...@@ -38,11 +38,18 @@ class Zone;
// A container for the inputs, configuration options, and outputs of parsing. // A container for the inputs, configuration options, and outputs of parsing.
class V8_EXPORT_PRIVATE ParseInfo { class V8_EXPORT_PRIVATE ParseInfo {
public: public:
ParseInfo(Isolate*); explicit ParseInfo(AccountingAllocator* zone_allocator);
explicit ParseInfo(Isolate*);
ParseInfo(Isolate*, AccountingAllocator* zone_allocator); ParseInfo(Isolate*, AccountingAllocator* zone_allocator);
ParseInfo(Isolate* isolate, Handle<Script> script); ParseInfo(Isolate* isolate, Handle<Script> script);
ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared); ParseInfo(Isolate* isolate, Handle<SharedFunctionInfo> shared);
// Creates a new parse info based on parent top-level |outer_parse_info| for
// function |literal|.
static std::unique_ptr<ParseInfo> FromParent(
const ParseInfo* outer_parse_info, AccountingAllocator* zone_allocator,
const FunctionLiteral* literal, const AstRawString* function_name);
~ParseInfo(); ~ParseInfo();
Handle<Script> CreateScript(Isolate* isolate, Handle<String> source, Handle<Script> CreateScript(Isolate* isolate, Handle<String> source,
...@@ -203,7 +210,7 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -203,7 +210,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
Handle<Script> script() const { return script_; } Handle<Script> script() const { return script_; }
void set_script(Handle<Script> script); void set_script(Handle<Script> script);
void ClearScriptHandle() { script_ = Handle<Script>(); }
MaybeHandle<ScopeInfo> maybe_outer_scope_info() const { MaybeHandle<ScopeInfo> maybe_outer_scope_info() const {
return maybe_outer_scope_info_; return maybe_outer_scope_info_;
} }
...@@ -225,6 +232,11 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -225,6 +232,11 @@ class V8_EXPORT_PRIVATE ParseInfo {
private: private:
void SetScriptForToplevelCompile(Isolate* isolate, Handle<Script> script); void SetScriptForToplevelCompile(Isolate* isolate, Handle<Script> script);
// Set function info flags based on those in either FunctionLiteral or
// SharedFunctionInfo |function|
template <typename T>
void SetFunctionInfo(T function);
// Various configuration flags for parsing. // Various configuration flags for parsing.
enum Flag { enum Flag {
// ---------- Input flags --------------------------- // ---------- Input flags ---------------------------
......
...@@ -723,11 +723,13 @@ ProducedPreParsedScopeData* ZoneConsumedPreParsedScopeData::GetChildData( ...@@ -723,11 +723,13 @@ ProducedPreParsedScopeData* ZoneConsumedPreParsedScopeData::GetChildData(
std::unique_ptr<ConsumedPreParsedScopeData> ConsumedPreParsedScopeData::For( std::unique_ptr<ConsumedPreParsedScopeData> ConsumedPreParsedScopeData::For(
Isolate* isolate, Handle<PreParsedScopeData> data) { Isolate* isolate, Handle<PreParsedScopeData> data) {
DCHECK(!data.is_null());
return base::make_unique<OnHeapConsumedPreParsedScopeData>(isolate, data); return base::make_unique<OnHeapConsumedPreParsedScopeData>(isolate, data);
} }
std::unique_ptr<ConsumedPreParsedScopeData> ConsumedPreParsedScopeData::For( std::unique_ptr<ConsumedPreParsedScopeData> ConsumedPreParsedScopeData::For(
Zone* zone, ZonePreParsedScopeData* data) { Zone* zone, ZonePreParsedScopeData* data) {
if (data == nullptr) return {};
return base::make_unique<ZoneConsumedPreParsedScopeData>(zone, data); return base::make_unique<ZoneConsumedPreParsedScopeData>(zone, data);
} }
......
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "include/v8-platform.h" #include "include/v8-platform.h"
#include "src/api-inl.h" #include "src/api-inl.h"
#include "src/ast/ast-value-factory.h" #include "src/ast/ast-value-factory.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/base/platform/semaphore.h" #include "src/base/platform/semaphore.h"
#include "src/base/template-utils.h" #include "src/base/template-utils.h"
#include "src/compiler-dispatcher/compiler-dispatcher-job.h" #include "src/compiler-dispatcher/compiler-dispatcher-job.h"
...@@ -25,16 +27,6 @@ ...@@ -25,16 +27,6 @@
#include "test/unittests/test-utils.h" #include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
// V8 is smart enough to know something was already compiled and return compiled
// code straight away. We need a unique name for each test function so that V8
// returns an empty SharedFunctionInfo.
#define _STR(x) #x
#define STR(x) _STR(x)
#define _SCRIPT(fn, a, b, c) a fn b fn c
#define SCRIPT(a, b, c) _SCRIPT("f" STR(__LINE__), a, b, c)
#define TEST_SCRIPT() \
"function f" STR(__LINE__) "(x, y) { return x * y }; f" STR(__LINE__) ";"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -77,6 +69,37 @@ class CompilerDispatcherTest : public TestWithNativeContext { ...@@ -77,6 +69,37 @@ class CompilerDispatcherTest : public TestWithNativeContext {
CompilerDispatcherTestFlags::RestoreFlags(); CompilerDispatcherTestFlags::RestoreFlags();
} }
static base::Optional<CompilerDispatcher::JobId> EnqueueUnoptimizedCompileJob(
CompilerDispatcher* dispatcher, Isolate* isolate,
Handle<SharedFunctionInfo> shared) {
std::unique_ptr<ParseInfo> outer_parse_info =
test::OuterParseInfoForShared(isolate, shared);
AstValueFactory* ast_value_factory =
outer_parse_info->GetOrCreateAstValueFactory();
AstNodeFactory ast_node_factory(ast_value_factory,
outer_parse_info->zone());
const AstRawString* function_name =
ast_value_factory->GetOneByteString("f");
DeclarationScope* script_scope = new (outer_parse_info->zone())
DeclarationScope(outer_parse_info->zone(), ast_value_factory);
DeclarationScope* function_scope =
new (outer_parse_info->zone()) DeclarationScope(
outer_parse_info->zone(), script_scope, FUNCTION_SCOPE);
function_scope->set_start_position(shared->StartPosition());
function_scope->set_end_position(shared->EndPosition());
const FunctionLiteral* function_literal =
ast_node_factory.NewFunctionLiteral(
function_name, function_scope, nullptr, -1, -1, -1,
FunctionLiteral::kNoDuplicateParameters,
FunctionLiteral::kAnonymousExpression,
FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
shared->FunctionLiteralId(isolate), nullptr);
return dispatcher->Enqueue(outer_parse_info.get(), function_name,
function_literal);
}
private: private:
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherTest); DISALLOW_COPY_AND_ASSIGN(CompilerDispatcherTest);
}; };
...@@ -313,17 +336,27 @@ TEST_F(CompilerDispatcherTest, IsEnqueued) { ...@@ -313,17 +336,27 @@ TEST_F(CompilerDispatcherTest, IsEnqueued) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_TRUE(job_id);
ASSERT_TRUE(dispatcher.Enqueue(shared)); ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); // SFI not yet registered.
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_TRUE(dispatcher.IsEnqueued(shared)); ASSERT_TRUE(dispatcher.IsEnqueued(shared));
dispatcher.AbortAll(BlockingBehavior::kBlock); dispatcher.AbortAll(BlockingBehavior::kBlock);
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearWorkerTasks();
platform.ClearIdleTask(); platform.ClearIdleTask();
} }
...@@ -331,79 +364,71 @@ TEST_F(CompilerDispatcherTest, FinishNow) { ...@@ -331,79 +364,71 @@ TEST_F(CompilerDispatcherTest, FinishNow) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(shared->is_compiled()); ASSERT_FALSE(shared->is_compiled());
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.FinishNow(shared)); ASSERT_TRUE(dispatcher.FinishNow(shared));
// Finishing removes the SFI from the queue. // Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled()); ASSERT_TRUE(shared->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, FinishAllNow) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
constexpr int num_funcs = 2;
Handle<JSFunction> f[num_funcs];
Handle<SharedFunctionInfo> shared[num_funcs];
for (int i = 0; i < num_funcs; ++i) {
std::stringstream ss;
ss << 'f' << STR(__LINE__) << '_' << i;
std::string func_name = ss.str();
std::string script("function f" + func_name + "(x, y) { return x * y }; f" +
func_name + ";");
f[i] = RunJS<JSFunction>(script.c_str());
shared[i] = Handle<SharedFunctionInfo>(f[i]->shared(), i_isolate());
ASSERT_FALSE(shared[i]->is_compiled());
ASSERT_TRUE(dispatcher.Enqueue(shared[i]));
}
dispatcher.FinishAllNow();
for (int i = 0; i < num_funcs; ++i) {
// Finishing removes the SFI from the queue.
ASSERT_FALSE(dispatcher.IsEnqueued(shared[i]));
ASSERT_TRUE(shared[i]->is_compiled());
}
platform.ClearIdleTask();
platform.ClearWorkerTasks(); platform.ClearWorkerTasks();
platform.ClearIdleTask();
} }
TEST_F(CompilerDispatcherTest, IdleTask) { TEST_F(CompilerDispatcherTest, IdleTask) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
// Since time doesn't progress on the MockPlatform, this is enough idle time // Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function. // to finish compiling the function.
platform.RunIdleTask(1000.0, 0.0); platform.RunIdleTask(1000.0, 0.0);
// Since we haven't yet registered the SFI for the job, it should still be
// enqueued and waiting.
ASSERT_TRUE(dispatcher.IsEnqueued(*job_id));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
// Register SFI, which should schedule another idle task to complete the
// compilation.
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(platform.IdleTaskPending());
platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled()); ASSERT_TRUE(shared->is_compiled());
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) { TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
// The job should be scheduled for the main thread. // The job should be scheduled for the main thread.
...@@ -419,9 +444,9 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) { ...@@ -419,9 +444,9 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
// The job should be still scheduled for the main thread, but ready for // The job should be still scheduled for the main thread, but ready for
// parsing. // finalization.
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared, ASSERT_EQ(UnoptimizedCompileJob::Status::kReadyToFinalize,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
// Now grant a lot of idle time and freeze time. // Now grant a lot of idle time and freeze time.
...@@ -430,25 +455,28 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) { ...@@ -430,25 +455,28 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled()); ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, IdleTaskException) { TEST_F(CompilerDispatcherTest, IdleTaskException) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50); CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string func_name("f" STR(__LINE__)); std::string raw_script("(x) { var a = ");
std::string script("function " + func_name + "(x) { var a = "); for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 500; i++) {
// Alternate + and - to avoid n-ary operation nodes. // Alternate + and - to avoid n-ary operation nodes.
script += "'x' + 'x' - "; raw_script += "'x' + 'x' - ";
} }
script += " 'x'; }; " + func_name + ";"; raw_script += " 'x'; };";
Handle<JSFunction> f = RunJS<JSFunction>(script.c_str()); test::ScriptResource* script =
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), script);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
// Since time doesn't progress on the MockPlatform, this is enough idle time // Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function. // to finish compiling the function.
...@@ -457,41 +485,33 @@ TEST_F(CompilerDispatcherTest, IdleTaskException) { ...@@ -457,41 +485,33 @@ TEST_F(CompilerDispatcherTest, IdleTaskException) {
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(i_isolate()->has_pending_exception()); ASSERT_FALSE(i_isolate()->has_pending_exception());
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, CompileOnBackgroundThread) { TEST_F(CompilerDispatcherTest, CompileOnBackgroundThread) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform()); platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending()); ASSERT_FALSE(platform.WorkerTasksPending());
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled, ASSERT_EQ(UnoptimizedCompileJob::Status::kReadyToFinalize,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
// Now grant a lot of idle time and freeze time. // Now grant a lot of idle time and freeze time.
...@@ -500,34 +520,30 @@ TEST_F(CompilerDispatcherTest, CompileOnBackgroundThread) { ...@@ -500,34 +520,30 @@ TEST_F(CompilerDispatcherTest, CompileOnBackgroundThread) {
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(shared->is_compiled()); ASSERT_TRUE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending());
} }
TEST_F(CompilerDispatcherTest, FinishNowWithWorkerTask) { TEST_F(CompilerDispatcherTest, FinishNowWithWorkerTask) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared)); ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
// This does not block, but races with the FinishNow() call below. // This does not block, but races with the FinishNow() call below.
...@@ -545,46 +561,54 @@ TEST_F(CompilerDispatcherTest, IdleTaskMultipleJobs) { ...@@ -545,46 +561,54 @@ TEST_F(CompilerDispatcherTest, IdleTaskMultipleJobs) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared_1 =
Handle<JSFunction> f1 = RunJS<JSFunction>(script1); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); ASSERT_FALSE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared_2 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_2->is_compiled());
const char script2[] = TEST_SCRIPT(); base::Optional<CompilerDispatcher::JobId> job_id_1 =
Handle<JSFunction> f2 = RunJS<JSFunction>(script2); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate()); base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
ASSERT_FALSE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
ASSERT_TRUE(dispatcher.Enqueue(shared1)); dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_TRUE(dispatcher.Enqueue(shared2));
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
// Since time doesn't progress on the MockPlatform, this is enough idle time // Since time doesn't progress on the MockPlatform, this is enough idle time
// to finish compiling the function. // to finish compiling the function.
platform.RunIdleTask(1000.0, 0.0); platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared1)); ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared2)); ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
ASSERT_TRUE(shared1->is_compiled()); ASSERT_TRUE(shared_1->is_compiled());
ASSERT_TRUE(shared2->is_compiled()); ASSERT_TRUE(shared_2->is_compiled());
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, FinishNowException) { TEST_F(CompilerDispatcherTest, FinishNowException) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, 50); CompilerDispatcher dispatcher(i_isolate(), &platform, 50);
std::string func_name("f" STR(__LINE__)); std::string raw_script("(x) { var a = ");
std::string script("function " + func_name + "(x) { var a = "); for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 500; i++) {
// Alternate + and - to avoid n-ary operation nodes. // Alternate + and - to avoid n-ary operation nodes.
script += "'x' + 'x' - "; raw_script += "'x' + 'x' - ";
} }
script += " 'x'; }; " + func_name + ";"; raw_script += " 'x'; };";
Handle<JSFunction> f = RunJS<JSFunction>(script.c_str()); test::ScriptResource* script =
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
Handle<SharedFunctionInfo> shared =
test::CreateSharedFunctionInfo(i_isolate(), script);
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_FALSE(dispatcher.FinishNow(shared)); ASSERT_FALSE(dispatcher.FinishNow(shared));
...@@ -594,34 +618,26 @@ TEST_F(CompilerDispatcherTest, FinishNowException) { ...@@ -594,34 +618,26 @@ TEST_F(CompilerDispatcherTest, FinishNowException) {
i_isolate()->clear_pending_exception(); i_isolate()->clear_pending_exception();
platform.ClearIdleTask(); platform.ClearIdleTask();
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, AsyncAbortAllPendingWorkerTask) { TEST_F(CompilerDispatcherTest, AsyncAbortAllPendingWorkerTask) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared)); ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
// The background task hasn't yet started, so we can just cancel it. // The background task hasn't yet started, so we can just cancel it.
...@@ -642,32 +658,23 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) { ...@@ -642,32 +658,23 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared_1 =
Handle<JSFunction> f1 = RunJS<JSFunction>(script1); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); ASSERT_FALSE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared_2 =
test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_FALSE(shared_2->is_compiled());
const char script2[] = TEST_SCRIPT(); base::Optional<CompilerDispatcher::JobId> job_id_1 =
Handle<JSFunction> f2 = RunJS<JSFunction>(script2); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate()); dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(dispatcher.Enqueue(shared1));
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
// Make compiling super expensive, and advance job as much as possible on the ASSERT_FALSE(shared_1->is_compiled());
// foreground thread. ASSERT_TRUE(platform.IdleTaskPending());
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared1));
ASSERT_FALSE(shared1->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
// Kick off background tasks and freeze them. // Kick off background tasks and freeze them.
...@@ -681,7 +688,9 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) { ...@@ -681,7 +688,9 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) {
ASSERT_TRUE(platform.ForegroundTasksPending()); ASSERT_TRUE(platform.ForegroundTasksPending());
// We can't schedule new tasks while we're aborting. // We can't schedule new tasks while we're aborting.
ASSERT_FALSE(dispatcher.Enqueue(shared2)); base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
ASSERT_FALSE(job_id_2);
// Run the first AbortTask. Since the background job is still pending, it // Run the first AbortTask. Since the background job is still pending, it
// can't do anything. // can't do anything.
...@@ -711,10 +720,14 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) { ...@@ -711,10 +720,14 @@ TEST_F(CompilerDispatcherTest, AsyncAbortAllRunningWorkerTask) {
ASSERT_FALSE(platform.ForegroundTasksPending()); ASSERT_FALSE(platform.ForegroundTasksPending());
// Now it's possible to enqueue new functions again. // Now it's possible to enqueue new functions again.
ASSERT_TRUE(dispatcher.Enqueue(shared2)); job_id_2 = EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
ASSERT_TRUE(job_id_2);
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
ASSERT_FALSE(platform.ForegroundTasksPending()); ASSERT_FALSE(platform.ForegroundTasksPending());
dispatcher.AbortAll(BlockingBehavior::kBlock);
platform.ClearWorkerTasks();
platform.ClearIdleTask(); platform.ClearIdleTask();
} }
...@@ -722,28 +735,20 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) { ...@@ -722,28 +735,20 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher.Enqueue(shared)); EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(platform.IdleTaskPending()); dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 1u); ASSERT_EQ(dispatcher.jobs_.size(), 1u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.IdleTaskPending());
// Make compiling super expensive, and advance job as much as possible on the
// foreground thread.
dispatcher.tracer_->RecordCompile(50000.0, 1);
platform.RunIdleTask(10.0, 0.0);
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
// Kick off background tasks and freeze them. // Kick off background tasks and freeze them.
...@@ -764,7 +769,12 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) { ...@@ -764,7 +769,12 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) {
ASSERT_TRUE(dispatcher.abort_); ASSERT_TRUE(dispatcher.abort_);
} }
// While the background thread holds on to a job, it is still enqueud. // Run the idle task, which should have already been canceled and won't do
// anything.
ASSERT_TRUE(platform.IdleTaskPending());
platform.RunIdleTask(5.0, 1.0);
// While the background thread holds on to a job, it is still enqueued.
ASSERT_TRUE(dispatcher.IsEnqueued(shared)); ASSERT_TRUE(dispatcher.IsEnqueued(shared));
// Release background task. // Release background task.
...@@ -783,7 +793,7 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) { ...@@ -783,7 +793,7 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) {
} }
ASSERT_TRUE(platform.ForegroundTasksPending()); ASSERT_TRUE(platform.ForegroundTasksPending());
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending()); ASSERT_FALSE(platform.WorkerTasksPending());
platform.RunForegroundTasks(); platform.RunForegroundTasks();
...@@ -791,32 +801,34 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) { ...@@ -791,32 +801,34 @@ TEST_F(CompilerDispatcherTest, FinishNowDuringAbortAll) {
base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); base::LockGuard<base::Mutex> lock(&dispatcher.mutex_);
ASSERT_FALSE(dispatcher.abort_); ASSERT_FALSE(dispatcher.abort_);
} }
platform.ClearForegroundTasks();
platform.ClearIdleTask();
} }
TEST_F(CompilerDispatcherTest, MemoryPressure) { TEST_F(CompilerDispatcherTest, MemoryPressure) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
// Can't enqueue tasks under memory pressure. // Can't enqueue tasks under memory pressure.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical, dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true); true);
ASSERT_FALSE(dispatcher.Enqueue(shared)); base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_FALSE(job_id);
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kNone, true); dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kNone, true);
ASSERT_TRUE(dispatcher.Enqueue(shared));
job_id = EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
ASSERT_TRUE(job_id);
// Memory pressure cancels current jobs. // Memory pressure cancels current jobs.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical, dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true); true);
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher.IsEnqueued(*job_id));
platform.ClearIdleTask(); platform.ClearIdleTask();
platform.ClearWorkerTasks();
} }
namespace { namespace {
...@@ -847,11 +859,14 @@ TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) { ...@@ -847,11 +859,14 @@ TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared =
Handle<JSFunction> f = RunJS<JSFunction>(script); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_FALSE(shared->is_compiled());
base::Optional<CompilerDispatcher::JobId> job_id =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared);
dispatcher.RegisterSharedFunctionInfo(*job_id, *shared);
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Semaphore sem(0); base::Semaphore sem(0);
V8::GetCurrentPlatform()->CallOnWorkerThread( V8::GetCurrentPlatform()->CallOnWorkerThread(
base::make_unique<PressureNotificationTask>(i_isolate(), &dispatcher, base::make_unique<PressureNotificationTask>(i_isolate(), &dispatcher,
...@@ -873,44 +888,6 @@ TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) { ...@@ -873,44 +888,6 @@ TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) {
ASSERT_FALSE(platform.ForegroundTasksPending()); ASSERT_FALSE(platform.ForegroundTasksPending());
platform.ClearIdleTask(); platform.ClearIdleTask();
}
TEST_F(CompilerDispatcherTest, EnqueueJob) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
std::unique_ptr<CompilerDispatcherJob> job(
new UnoptimizedCompileJob(i_isolate(), dispatcher.tracer_.get(), shared,
dispatcher.max_stack_size_));
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
dispatcher.Enqueue(std::move(job));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
ASSERT_FALSE(platform.WorkerTasksPending());
}
TEST_F(CompilerDispatcherTest, EnqueueAndStep) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT();
Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.IdleTaskPending());
platform.ClearIdleTask();
ASSERT_TRUE(platform.WorkerTasksPending());
platform.ClearWorkerTasks(); platform.ClearWorkerTasks();
} }
...@@ -919,14 +896,16 @@ TEST_F(CompilerDispatcherTest, CompileLazyFinishesDispatcherJob) { ...@@ -919,14 +896,16 @@ TEST_F(CompilerDispatcherTest, CompileLazyFinishesDispatcherJob) {
// enqueued functions. // enqueued functions.
CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher(); CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
const char script[] = "function lazy() { return 42; }; lazy;"; const char raw_script[] = "function lazy() { return 42; }; lazy;";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script); Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_FALSE(shared->is_compiled()); ASSERT_FALSE(shared->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared));
ASSERT_TRUE(dispatcher->Enqueue(shared)); base::Optional<CompilerDispatcher::JobId> job_id =
ASSERT_TRUE(dispatcher->IsEnqueued(shared)); EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared);
dispatcher->RegisterSharedFunctionInfo(*job_id, *shared);
// Now force the function to run and ensure CompileLazy finished and dequeues // Now force the function to run and ensure CompileLazy finished and dequeues
// it from the dispatcher. // it from the dispatcher.
...@@ -940,66 +919,57 @@ TEST_F(CompilerDispatcherTest, CompileLazy2FinishesDispatcherJob) { ...@@ -940,66 +919,57 @@ TEST_F(CompilerDispatcherTest, CompileLazy2FinishesDispatcherJob) {
// enqueued functions. // enqueued functions.
CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher(); CompilerDispatcher* dispatcher = i_isolate()->compiler_dispatcher();
const char source2[] = "function lazy2() { return 42; }; lazy2;"; const char raw_source_2[] = "function lazy2() { return 42; }; lazy2;";
Handle<JSFunction> lazy2 = RunJS<JSFunction>(source2); test::ScriptResource* source_2 =
Handle<SharedFunctionInfo> shared2(lazy2->shared(), i_isolate()); new test::ScriptResource(raw_source_2, strlen(raw_source_2));
ASSERT_FALSE(shared2->is_compiled()); Handle<JSFunction> lazy2 = RunJS<JSFunction>(source_2);
Handle<SharedFunctionInfo> shared_2(lazy2->shared(), i_isolate());
ASSERT_FALSE(shared_2->is_compiled());
const char source1[] = "function lazy1() { return lazy2(); }; lazy1;"; const char raw_source_1[] = "function lazy1() { return lazy2(); }; lazy1;";
Handle<JSFunction> lazy1 = RunJS<JSFunction>(source1); test::ScriptResource* source_1 =
Handle<SharedFunctionInfo> shared1(lazy1->shared(), i_isolate()); new test::ScriptResource(raw_source_1, strlen(raw_source_1));
ASSERT_FALSE(shared1->is_compiled()); Handle<JSFunction> lazy1 = RunJS<JSFunction>(source_1);
Handle<SharedFunctionInfo> shared_1(lazy1->shared(), i_isolate());
ASSERT_FALSE(shared_1->is_compiled());
ASSERT_TRUE(dispatcher->Enqueue(shared1)); base::Optional<CompilerDispatcher::JobId> job_id_1 =
ASSERT_TRUE(dispatcher->Enqueue(shared2)); EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_1);
dispatcher->RegisterSharedFunctionInfo(*job_id_1, *shared_1);
RunJS("lazy1();"); base::Optional<CompilerDispatcher::JobId> job_id_2 =
ASSERT_TRUE(shared1->is_compiled()); EnqueueUnoptimizedCompileJob(dispatcher, i_isolate(), shared_2);
ASSERT_TRUE(shared2->is_compiled()); dispatcher->RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_FALSE(dispatcher->IsEnqueued(shared1));
ASSERT_FALSE(dispatcher->IsEnqueued(shared2));
}
TEST_F(CompilerDispatcherTest, EnqueueAndStepTwice) { ASSERT_TRUE(dispatcher->IsEnqueued(shared_1));
MockPlatform platform; ASSERT_TRUE(dispatcher->IsEnqueued(shared_2));
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] = TEST_SCRIPT(); RunJS("lazy1();");
Handle<JSFunction> f = RunJS<JSFunction>(script); ASSERT_TRUE(shared_1->is_compiled());
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); ASSERT_TRUE(shared_2->is_compiled());
ASSERT_FALSE(dispatcher->IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared)); ASSERT_FALSE(dispatcher->IsEnqueued(shared_2));
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
// EnqueueAndStep of the same function again (shouldn't step the job.
ASSERT_TRUE(dispatcher.EnqueueAndStep(shared));
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending());
platform.ClearIdleTask();
platform.ClearWorkerTasks();
} }
TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) { TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
MockPlatform platform; MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script1[] = TEST_SCRIPT(); Handle<SharedFunctionInfo> shared_1 =
Handle<JSFunction> f1 = RunJS<JSFunction>(script1); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); ASSERT_FALSE(shared_1->is_compiled());
const char script2[] = TEST_SCRIPT();
Handle<JSFunction> f2 = RunJS<JSFunction>(script2);
Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate());
ASSERT_FALSE(platform.IdleTaskPending()); Handle<SharedFunctionInfo> shared_2 =
ASSERT_TRUE(dispatcher.Enqueue(shared1)); test::CreateSharedFunctionInfo(i_isolate(), nullptr);
ASSERT_TRUE(dispatcher.Enqueue(shared2)); ASSERT_FALSE(shared_2->is_compiled());
ASSERT_TRUE(platform.IdleTaskPending());
base::Optional<CompilerDispatcher::JobId> job_id_1 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_1);
dispatcher.RegisterSharedFunctionInfo(*job_id_1, *shared_1);
base::Optional<CompilerDispatcher::JobId> job_id_2 =
EnqueueUnoptimizedCompileJob(&dispatcher, i_isolate(), shared_2);
dispatcher.RegisterSharedFunctionInfo(*job_id_2, *shared_2);
ASSERT_EQ(dispatcher.jobs_.size(), 2u); ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
...@@ -1007,21 +977,11 @@ TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) { ...@@ -1007,21 +977,11 @@ TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial, ASSERT_EQ(UnoptimizedCompileJob::Status::kInitial,
(++dispatcher.jobs_.begin())->second->status()); (++dispatcher.jobs_.begin())->second->status());
// Make compiling super expensive, and advance job as much as possible on the ASSERT_TRUE(dispatcher.IsEnqueued(shared_1));
// foreground thread. ASSERT_TRUE(dispatcher.IsEnqueued(shared_2));
dispatcher.tracer_->RecordCompile(50000.0, 1); ASSERT_FALSE(shared_1->is_compiled());
platform.RunIdleTask(10.0, 0.0); ASSERT_FALSE(shared_2->is_compiled());
ASSERT_EQ(dispatcher.jobs_.size(), 2u); ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
dispatcher.jobs_.begin()->second->status());
ASSERT_EQ(UnoptimizedCompileJob::Status::kPrepared,
(++dispatcher.jobs_.begin())->second->status());
ASSERT_TRUE(dispatcher.IsEnqueued(shared1));
ASSERT_TRUE(dispatcher.IsEnqueued(shared2));
ASSERT_FALSE(shared1->is_compiled());
ASSERT_FALSE(shared2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending());
ASSERT_TRUE(platform.WorkerTasksPending()); ASSERT_TRUE(platform.WorkerTasksPending());
platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform()); platform.RunWorkerTasksAndBlock(V8::GetCurrentPlatform());
...@@ -1029,26 +989,20 @@ TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) { ...@@ -1029,26 +989,20 @@ TEST_F(CompilerDispatcherTest, CompileMultipleOnBackgroundThread) {
ASSERT_TRUE(platform.IdleTaskPending()); ASSERT_TRUE(platform.IdleTaskPending());
ASSERT_FALSE(platform.WorkerTasksPending()); ASSERT_FALSE(platform.WorkerTasksPending());
ASSERT_EQ(dispatcher.jobs_.size(), 2u); ASSERT_EQ(dispatcher.jobs_.size(), 2u);
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled, ASSERT_EQ(UnoptimizedCompileJob::Status::kReadyToFinalize,
dispatcher.jobs_.begin()->second->status()); dispatcher.jobs_.begin()->second->status());
ASSERT_EQ(UnoptimizedCompileJob::Status::kCompiled, ASSERT_EQ(UnoptimizedCompileJob::Status::kReadyToFinalize,
(++dispatcher.jobs_.begin())->second->status()); (++dispatcher.jobs_.begin())->second->status());
// Now grant a lot of idle time and freeze time. // Now grant a lot of idle time and freeze time.
platform.RunIdleTask(1000.0, 0.0); platform.RunIdleTask(1000.0, 0.0);
ASSERT_FALSE(dispatcher.IsEnqueued(shared1)); ASSERT_FALSE(dispatcher.IsEnqueued(shared_1));
ASSERT_FALSE(dispatcher.IsEnqueued(shared2)); ASSERT_FALSE(dispatcher.IsEnqueued(shared_2));
ASSERT_TRUE(shared1->is_compiled()); ASSERT_TRUE(shared_1->is_compiled());
ASSERT_TRUE(shared2->is_compiled()); ASSERT_TRUE(shared_2->is_compiled());
ASSERT_FALSE(platform.IdleTaskPending()); ASSERT_FALSE(platform.IdleTaskPending());
} }
#undef _STR
#undef STR
#undef _SCRIPT
#undef SCRIPT
#undef TEST_SCRIPT
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "src/flags.h" #include "src/flags.h"
#include "src/isolate-inl.h" #include "src/isolate-inl.h"
#include "src/parsing/parse-info.h" #include "src/parsing/parse-info.h"
#include "src/parsing/preparsed-scope-data.h"
#include "src/v8.h" #include "src/v8.h"
#include "test/unittests/test-helpers.h" #include "test/unittests/test-helpers.h"
#include "test/unittests/test-utils.h" #include "test/unittests/test-utils.h"
...@@ -26,9 +27,11 @@ namespace internal { ...@@ -26,9 +27,11 @@ namespace internal {
class UnoptimizedCompileJobTest : public TestWithNativeContext { class UnoptimizedCompileJobTest : public TestWithNativeContext {
public: public:
UnoptimizedCompileJobTest() : tracer_(isolate()) {} UnoptimizedCompileJobTest()
: tracer_(isolate()), allocator_(isolate()->allocator()) {}
~UnoptimizedCompileJobTest() override = default; ~UnoptimizedCompileJobTest() override = default;
AccountingAllocator* allocator() { return allocator_; }
CompilerDispatcherTracer* tracer() { return &tracer_; } CompilerDispatcherTracer* tracer() { return &tracer_; }
static void SetUpTestCase() { static void SetUpTestCase() {
...@@ -51,8 +54,43 @@ class UnoptimizedCompileJobTest : public TestWithNativeContext { ...@@ -51,8 +54,43 @@ class UnoptimizedCompileJobTest : public TestWithNativeContext {
return job->parse_info_->literal()->scope()->Lookup(name_raw_string); return job->parse_info_->literal()->scope()->Lookup(name_raw_string);
} }
UnoptimizedCompileJob* NewUnoptimizedCompileJob(
Isolate* isolate, Handle<SharedFunctionInfo> shared,
size_t stack_size = FLAG_stack_size) {
std::unique_ptr<ParseInfo> outer_parse_info =
test::OuterParseInfoForShared(isolate, shared);
AstValueFactory* ast_value_factory =
outer_parse_info->GetOrCreateAstValueFactory();
AstNodeFactory ast_node_factory(ast_value_factory,
outer_parse_info->zone());
const AstRawString* function_name =
ast_value_factory->GetOneByteString("f");
DeclarationScope* script_scope = new (outer_parse_info->zone())
DeclarationScope(outer_parse_info->zone(), ast_value_factory);
DeclarationScope* function_scope =
new (outer_parse_info->zone()) DeclarationScope(
outer_parse_info->zone(), script_scope, FUNCTION_SCOPE);
function_scope->set_start_position(shared->StartPosition());
function_scope->set_end_position(shared->EndPosition());
const FunctionLiteral* function_literal =
ast_node_factory.NewFunctionLiteral(
function_name, function_scope, nullptr, -1, -1, -1,
FunctionLiteral::kNoDuplicateParameters,
FunctionLiteral::kAnonymousExpression,
FunctionLiteral::kShouldEagerCompile, shared->StartPosition(), true,
shared->FunctionLiteralId(isolate), nullptr);
return new UnoptimizedCompileJob(
tracer(), allocator(), outer_parse_info.get(), function_name,
function_literal,
isolate->counters()->worker_thread_runtime_call_stats(),
FLAG_stack_size);
}
private: private:
CompilerDispatcherTracer tracer_; CompilerDispatcherTracer tracer_;
AccountingAllocator* allocator_;
static SaveFlags* save_flags_; static SaveFlags* save_flags_;
DISALLOW_COPY_AND_ASSIGN(UnoptimizedCompileJobTest); DISALLOW_COPY_AND_ASSIGN(UnoptimizedCompileJobTest);
...@@ -63,24 +101,25 @@ SaveFlags* UnoptimizedCompileJobTest::save_flags_ = nullptr; ...@@ -63,24 +101,25 @@ SaveFlags* UnoptimizedCompileJobTest::save_flags_ = nullptr;
#define ASSERT_JOB_STATUS(STATUS, JOB) ASSERT_EQ(STATUS, JOB->status()) #define ASSERT_JOB_STATUS(STATUS, JOB) ASSERT_EQ(STATUS, JOB->status())
TEST_F(UnoptimizedCompileJobTest, Construct) { TEST_F(UnoptimizedCompileJobTest, Construct) {
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), nullptr), test::CreateSharedFunctionInfo(isolate(), nullptr);
FLAG_stack_size)); ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
} }
TEST_F(UnoptimizedCompileJobTest, StateTransitions) { TEST_F(UnoptimizedCompileJobTest, StateTransitions) {
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), nullptr), test::CreateSharedFunctionInfo(isolate(), nullptr);
FLAG_stack_size)); ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kPrepared, job);
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kCompiled, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate()); job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
job->ResetOnMainThread(isolate()); job->ResetOnMainThread(isolate());
...@@ -89,15 +128,16 @@ TEST_F(UnoptimizedCompileJobTest, StateTransitions) { ...@@ -89,15 +128,16 @@ TEST_F(UnoptimizedCompileJobTest, StateTransitions) {
TEST_F(UnoptimizedCompileJobTest, SyntaxError) { TEST_F(UnoptimizedCompileJobTest, SyntaxError) {
test::ScriptResource* script = new test::ScriptResource("^^^", strlen("^^^")); test::ScriptResource* script = new test::ScriptResource("^^^", strlen("^^^"));
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), test::CreateSharedFunctionInfo(isolate(), script);
FLAG_stack_size)); std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
job->ReportErrorsOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed()); ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception()); ASSERT_TRUE(isolate()->has_pending_exception());
...@@ -109,7 +149,7 @@ TEST_F(UnoptimizedCompileJobTest, SyntaxError) { ...@@ -109,7 +149,7 @@ TEST_F(UnoptimizedCompileJobTest, SyntaxError) {
} }
TEST_F(UnoptimizedCompileJobTest, CompileAndRun) { TEST_F(UnoptimizedCompileJobTest, CompileAndRun) {
const char script[] = const char raw_script[] =
"function g() {\n" "function g() {\n"
" f = function(a) {\n" " f = function(a) {\n"
" for (var i = 0; i < 3; i++) { a += 20; }\n" " for (var i = 0; i < 3; i++) { a += 20; }\n"
...@@ -118,29 +158,28 @@ TEST_F(UnoptimizedCompileJobTest, CompileAndRun) { ...@@ -118,29 +158,28 @@ TEST_F(UnoptimizedCompileJobTest, CompileAndRun) {
" return f;\n" " return f;\n"
"}\n" "}\n"
"g();"; "g();";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script); Handle<JSFunction> f = RunJS<JSFunction>(script);
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared = handle(f->shared(), isolate());
isolate(), tracer(), handle(f->shared(), f->GetIsolate()), ASSERT_FALSE(shared->is_compiled());
FLAG_stack_size)); std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); job->FinalizeOnMainThread(isolate(), shared);
job->FinalizeOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
ASSERT_TRUE(shared->is_compiled());
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
Smi* value = Smi::cast(*RunJS("f(100);")); Smi* value = Smi::cast(*RunJS("f(100);"));
ASSERT_TRUE(value == Smi::FromInt(160)); ASSERT_TRUE(value == Smi::FromInt(160));
job->ResetOnMainThread(isolate());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
} }
TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) { TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) {
std::string raw_script("() { var a = "); std::string raw_script("() { var a = ");
for (int i = 0; i < 500000; i++) { for (int i = 0; i < 10000; i++) {
// TODO(leszeks): Figure out a more "unit-test-y" way of forcing an analysis // TODO(leszeks): Figure out a more "unit-test-y" way of forcing an analysis
// failure than a binop stack overflow. // failure than a binop stack overflow.
...@@ -150,15 +189,16 @@ TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) { ...@@ -150,15 +189,16 @@ TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) {
raw_script += " 'x'; }"; raw_script += " 'x'; }";
test::ScriptResource* script = test::ScriptResource* script =
new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str())); new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), test::CreateSharedFunctionInfo(isolate(), script);
100)); std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared, 100));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
job->ReportErrorsOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed()); ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception()); ASSERT_TRUE(isolate()->has_pending_exception());
...@@ -170,22 +210,23 @@ TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) { ...@@ -170,22 +210,23 @@ TEST_F(UnoptimizedCompileJobTest, CompileFailureToAnalyse) {
TEST_F(UnoptimizedCompileJobTest, CompileFailureToFinalize) { TEST_F(UnoptimizedCompileJobTest, CompileFailureToFinalize) {
std::string raw_script("() { var a = "); std::string raw_script("() { var a = ");
for (int i = 0; i < 500; i++) { for (int i = 0; i < 5000; i++) {
// Alternate + and - to avoid n-ary operation nodes. // Alternate + and - to avoid n-ary operation nodes.
raw_script += "'x' + 'x' - "; raw_script += "'x' + 'x' - ";
} }
raw_script += " 'x'; }"; raw_script += " 'x'; }";
test::ScriptResource* script = test::ScriptResource* script =
new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str())); new test::ScriptResource(raw_script.c_str(), strlen(raw_script.c_str()));
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), test::CreateSharedFunctionInfo(isolate(), script);
50)); std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared, 50));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
job->ReportErrorsOnMainThread(isolate()); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kReadyToFinalize, job);
job->FinalizeOnMainThread(isolate(), shared);
ASSERT_TRUE(job->IsFailed()); ASSERT_TRUE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kFailed, job);
ASSERT_TRUE(isolate()->has_pending_exception()); ASSERT_TRUE(isolate()->has_pending_exception());
...@@ -223,19 +264,18 @@ TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) { ...@@ -223,19 +264,18 @@ TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) {
"}"; "}";
test::ScriptResource* script = test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script)); new test::ScriptResource(raw_script, strlen(raw_script));
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob( Handle<SharedFunctionInfo> shared =
isolate(), tracer(), test::CreateSharedFunctionInfo(isolate(), script), test::CreateSharedFunctionInfo(isolate(), script);
100)); std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
base::Semaphore semaphore(0); base::Semaphore semaphore(0);
auto background_task = base::make_unique<CompileTask>(job.get(), &semaphore); auto background_task = base::make_unique<CompileTask>(job.get(), &semaphore);
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kPrepared, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kInitial, job);
V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(background_task)); V8::GetCurrentPlatform()->CallOnWorkerThread(std::move(background_task));
semaphore.Wait(); semaphore.Wait();
job->FinalizeOnMainThread(isolate()); job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
...@@ -244,25 +284,29 @@ TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) { ...@@ -244,25 +284,29 @@ TEST_F(UnoptimizedCompileJobTest, CompileOnBackgroundThread) {
} }
TEST_F(UnoptimizedCompileJobTest, LazyInnerFunctions) { TEST_F(UnoptimizedCompileJobTest, LazyInnerFunctions) {
const char script[] = const char raw_script[] =
"f = function() {\n" "function g() {\n"
" e = (function() { return 42; });\n" " f = function() {\n"
" return e;\n" " e = (function() { return 42; });\n"
"};\n" " return e;\n"
"f;"; " }\n"
" return f;\n"
"}\n"
"g();";
test::ScriptResource* script =
new test::ScriptResource(raw_script, strlen(raw_script));
Handle<JSFunction> f = RunJS<JSFunction>(script); Handle<JSFunction> f = RunJS<JSFunction>(script);
Handle<SharedFunctionInfo> shared = handle(f->shared(), isolate());
ASSERT_FALSE(shared->is_compiled());
std::unique_ptr<UnoptimizedCompileJob> job(
NewUnoptimizedCompileJob(isolate(), shared));
std::unique_ptr<UnoptimizedCompileJob> job(new UnoptimizedCompileJob(
isolate(), tracer(), handle(f->shared(), f->GetIsolate()),
FLAG_stack_size));
job->PrepareOnMainThread(isolate());
ASSERT_FALSE(job->IsFailed());
job->Compile(false); job->Compile(false);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
job->FinalizeOnMainThread(isolate()); job->FinalizeOnMainThread(isolate(), shared);
ASSERT_FALSE(job->IsFailed()); ASSERT_FALSE(job->IsFailed());
ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job); ASSERT_JOB_STATUS(CompilerDispatcherJob::Status::kDone, job);
ASSERT_TRUE(shared->is_compiled());
Handle<JSFunction> e = RunJS<JSFunction>("f();"); Handle<JSFunction> e = RunJS<JSFunction>("f();");
......
...@@ -6,10 +6,13 @@ ...@@ -6,10 +6,13 @@
#include "include/v8.h" #include "include/v8.h"
#include "src/api.h" #include "src/api.h"
#include "src/base/template-utils.h"
#include "src/handles.h" #include "src/handles.h"
#include "src/isolate.h" #include "src/isolate.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/objects.h" #include "src/objects.h"
#include "src/parsing/scanner-character-streams.h"
#include "src/parsing/scanner.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -17,13 +20,13 @@ namespace test { ...@@ -17,13 +20,13 @@ namespace test {
Handle<String> CreateSource(Isolate* isolate, Handle<String> CreateSource(Isolate* isolate,
ExternalOneByteString::Resource* maybe_resource) { ExternalOneByteString::Resource* maybe_resource) {
static const char test_script[] = "(x) { x*x; }"; if (!maybe_resource) {
if (maybe_resource) { static const char test_script[] = "(x) { x*x; }";
return isolate->factory() maybe_resource = new test::ScriptResource(test_script, strlen(test_script));
->NewExternalStringFromOneByte(maybe_resource)
.ToHandleChecked();
} }
return isolate->factory()->NewStringFromAsciiChecked(test_script); return isolate->factory()
->NewExternalStringFromOneByte(maybe_resource)
.ToHandleChecked();
} }
Handle<SharedFunctionInfo> CreateSharedFunctionInfo( Handle<SharedFunctionInfo> CreateSharedFunctionInfo(
...@@ -51,6 +54,23 @@ Handle<SharedFunctionInfo> CreateSharedFunctionInfo( ...@@ -51,6 +54,23 @@ Handle<SharedFunctionInfo> CreateSharedFunctionInfo(
return scope.CloseAndEscape(shared); return scope.CloseAndEscape(shared);
} }
std::unique_ptr<ParseInfo> OuterParseInfoForShared(
Isolate* isolate, Handle<SharedFunctionInfo> shared) {
Handle<Script> script =
Handle<Script>::cast(handle(shared->script(), isolate));
std::unique_ptr<ParseInfo> result =
base::make_unique<ParseInfo>(isolate, script);
// Create a character stream to simulate the parser having done so for the
// to-level ParseProgram.
Handle<String> source(String::cast(script->source()), isolate);
std::unique_ptr<Utf16CharacterStream> stream(
ScannerStream::For(isolate, source));
result->set_character_stream(std::move(stream));
return result;
}
} // namespace test } // namespace test
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -46,6 +46,8 @@ Handle<String> CreateSource( ...@@ -46,6 +46,8 @@ Handle<String> CreateSource(
Handle<SharedFunctionInfo> CreateSharedFunctionInfo( Handle<SharedFunctionInfo> CreateSharedFunctionInfo(
Isolate* isolate, Isolate* isolate,
v8::String::ExternalOneByteStringResource* maybe_resource); v8::String::ExternalOneByteStringResource* maybe_resource);
std::unique_ptr<ParseInfo> OuterParseInfoForShared(
Isolate* isolate, Handle<SharedFunctionInfo> shared);
} // namespace test } // namespace test
} // namespace internal } // namespace internal
......
...@@ -62,6 +62,16 @@ Local<Value> TestWithIsolate::RunJS(const char* source) { ...@@ -62,6 +62,16 @@ Local<Value> TestWithIsolate::RunJS(const char* source) {
return script->Run(isolate()->GetCurrentContext()).ToLocalChecked(); return script->Run(isolate()->GetCurrentContext()).ToLocalChecked();
} }
Local<Value> TestWithIsolate::RunJS(
String::ExternalOneByteStringResource* source) {
Local<Script> script =
v8::Script::Compile(
isolate()->GetCurrentContext(),
v8::String::NewExternalOneByte(isolate(), source).ToLocalChecked())
.ToLocalChecked();
return script->Run(isolate()->GetCurrentContext()).ToLocalChecked();
}
TestWithContext::TestWithContext() TestWithContext::TestWithContext()
: context_(Context::New(isolate())), context_scope_(context_) {} : context_(Context::New(isolate())), context_scope_(context_) {}
...@@ -93,6 +103,11 @@ Handle<Object> TestWithIsolate::RunJSInternal(const char* source) { ...@@ -93,6 +103,11 @@ Handle<Object> TestWithIsolate::RunJSInternal(const char* source) {
return Utils::OpenHandle(*::v8::TestWithIsolate::RunJS(source)); return Utils::OpenHandle(*::v8::TestWithIsolate::RunJS(source));
} }
Handle<Object> TestWithIsolate::RunJSInternal(
::v8::String::ExternalOneByteStringResource* source) {
return Utils::OpenHandle(*::v8::TestWithIsolate::RunJS(source));
}
base::RandomNumberGenerator* TestWithIsolate::random_number_generator() const { base::RandomNumberGenerator* TestWithIsolate::random_number_generator() const {
return isolate()->random_number_generator(); return isolate()->random_number_generator();
} }
......
...@@ -37,6 +37,7 @@ class TestWithIsolate : public virtual ::testing::Test { ...@@ -37,6 +37,7 @@ class TestWithIsolate : public virtual ::testing::Test {
} }
Local<Value> RunJS(const char* source); Local<Value> RunJS(const char* source);
Local<Value> RunJS(String::ExternalOneByteStringResource* source);
static void SetUpTestCase(); static void SetUpTestCase();
static void TearDownTestCase(); static void TearDownTestCase();
...@@ -88,6 +89,13 @@ class TestWithIsolate : public virtual ::v8::TestWithIsolate { ...@@ -88,6 +89,13 @@ class TestWithIsolate : public virtual ::v8::TestWithIsolate {
return Handle<T>::cast(RunJSInternal(source)); return Handle<T>::cast(RunJSInternal(source));
} }
Handle<Object> RunJSInternal(const char* source); Handle<Object> RunJSInternal(const char* source);
template <typename T = Object>
Handle<T> RunJS(::v8::String::ExternalOneByteStringResource* source) {
return Handle<T>::cast(RunJSInternal(source));
}
Handle<Object> RunJSInternal(
::v8::String::ExternalOneByteStringResource* source);
base::RandomNumberGenerator* random_number_generator() const; base::RandomNumberGenerator* random_number_generator() const;
private: private:
......
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