| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/compiler-dispatcher/unoptimized-compile-job.h" |
| |
| #include "src/assert-scope.h" |
| #include "src/base/optional.h" |
| #include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" |
| #include "src/compiler.h" |
| #include "src/flags.h" |
| #include "src/global-handles.h" |
| #include "src/interpreter/interpreter.h" |
| #include "src/isolate.h" |
| #include "src/objects-inl.h" |
| #include "src/parsing/parse-info.h" |
| #include "src/parsing/parser.h" |
| #include "src/parsing/scanner-character-streams.h" |
| #include "src/unicode-cache.h" |
| #include "src/unoptimized-compilation-info.h" |
| #include "src/utils.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| namespace { |
| |
| class OneByteWrapper : public v8::String::ExternalOneByteStringResource { |
| public: |
| OneByteWrapper(const void* data, int length) : data_(data), length_(length) {} |
| ~OneByteWrapper() override = default; |
| |
| const char* data() const override { |
| return reinterpret_cast<const char*>(data_); |
| } |
| |
| size_t length() const override { return static_cast<size_t>(length_); } |
| |
| 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: |
| const void* data_; |
| int length_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TwoByteWrapper); |
| }; |
| |
| } // namespace |
| |
| UnoptimizedCompileJob::UnoptimizedCompileJob(Isolate* isolate, |
| CompilerDispatcherTracer* tracer, |
| Handle<SharedFunctionInfo> shared, |
| size_t max_stack_size) |
| : CompilerDispatcherJob(Type::kUnoptimizedCompile), |
| main_thread_id_(isolate->thread_id().ToInteger()), |
| tracer_(tracer), |
| allocator_(isolate->allocator()), |
| context_(isolate->global_handles()->Create(isolate->context())), |
| shared_(isolate->global_handles()->Create(*shared)), |
| max_stack_size_(max_stack_size), |
| trace_compiler_dispatcher_jobs_(FLAG_trace_compiler_dispatcher_jobs) { |
| DCHECK(!shared_->is_toplevel()); |
| // TODO(rmcilroy): Handle functions with non-empty outer scope info. |
| DCHECK(!shared_->HasOuterScopeInfo()); |
| HandleScope scope(isolate); |
| Handle<Script> script(Script::cast(shared_->script()), isolate); |
| Handle<String> source(String::cast(script->source()), isolate); |
| if (trace_compiler_dispatcher_jobs_) { |
| PrintF("UnoptimizedCompileJob[%p] created for ", static_cast<void*>(this)); |
| ShortPrintOnMainThread(); |
| PrintF(" in initial state.\n"); |
| } |
| } |
| |
| UnoptimizedCompileJob::~UnoptimizedCompileJob() { |
| DCHECK(status() == Status::kInitial || status() == Status::kDone); |
| if (!shared_.is_null()) { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| i::GlobalHandles::Destroy(Handle<Object>::cast(shared_).location()); |
| } |
| if (!context_.is_null()) { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| 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_); |
| parse_info_.reset(parse_info); |
| |
| unicode_cache_.reset(new UnicodeCache()); |
| parse_info_->set_unicode_cache(unicode_cache_.get()); |
| parse_info_->set_function_literal_id(shared_->FunctionLiteralId(isolate)); |
| if (V8_UNLIKELY(FLAG_runtime_stats)) { |
| parse_info_->set_runtime_call_stats(new (parse_info_->zone()) |
| RuntimeCallStats()); |
| } |
| |
| Handle<Script> script = parse_info->script(); |
| HandleScope scope(isolate); |
| |
| DCHECK(script->type() != Script::TYPE_NATIVE); |
| Handle<String> source(String::cast(script->source()), isolate); |
| if (source->IsExternalTwoByteString() || source->IsExternalOneByteString()) { |
| std::unique_ptr<Utf16CharacterStream> stream(ScannerStream::For( |
| isolate, source, shared_->StartPosition(), shared_->EndPosition())); |
| 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)); |
| } |
| |
| parser_.reset(new Parser(parse_info_.get())); |
| parser_->DeserializeScopeChain(isolate, parse_info_.get(), |
| parse_info_->maybe_outer_scope_info()); |
| |
| // Initailize the name after setting up the ast_value_factory. |
| Handle<String> name(shared_->Name(), isolate); |
| parse_info_->set_function_name( |
| parse_info_->ast_value_factory()->GetString(name)); |
| |
| set_status(Status::kPrepared); |
| } |
| |
| void UnoptimizedCompileJob::Compile(bool on_background_thread) { |
| DCHECK_EQ(status(), Status::kPrepared); |
| COMPILER_DISPATCHER_TRACE_SCOPE_WITH_NUM( |
| tracer_, kCompile, |
| parse_info_->end_position() - parse_info_->start_position()); |
| if (trace_compiler_dispatcher_jobs_) { |
| PrintF("UnoptimizedCompileJob[%p]: Compiling\n", static_cast<void*>(this)); |
| } |
| |
| DisallowHeapAllocation no_allocation; |
| DisallowHandleAllocation no_handles; |
| DisallowHandleDereference no_deref; |
| |
| parse_info_->set_on_background_thread(on_background_thread); |
| uintptr_t stack_limit = GetCurrentStackPosition() - max_stack_size_ * KB; |
| parser_->set_stack_limit(stack_limit); |
| parse_info_->set_stack_limit(stack_limit); |
| parser_->ParseOnBackground(parse_info_.get()); |
| |
| if (parse_info_->literal() == nullptr) { |
| // Parser sets error in pending error handler. |
| set_status(Status::kHasErrorsToReport); |
| return; |
| } |
| |
| if (!Compiler::Analyze(parse_info_.get())) { |
| parse_info_->pending_error_handler()->set_stack_overflow(); |
| set_status(Status::kHasErrorsToReport); |
| return; |
| } |
| |
| compilation_job_.reset(interpreter::Interpreter::NewCompilationJob( |
| parse_info_.get(), parse_info_->literal(), allocator_, nullptr)); |
| |
| if (!compilation_job_.get()) { |
| parse_info_->pending_error_handler()->set_stack_overflow(); |
| set_status(Status::kHasErrorsToReport); |
| return; |
| } |
| |
| if (compilation_job_->ExecuteJob() != CompilationJob::SUCCEEDED) { |
| parse_info_->pending_error_handler()->set_stack_overflow(); |
| set_status(Status::kHasErrorsToReport); |
| return; |
| } |
| |
| set_status(Status::kCompiled); |
| } |
| |
| void UnoptimizedCompileJob::FinalizeOnMainThread(Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); |
| DCHECK_EQ(status(), Status::kCompiled); |
| DCHECK_NOT_NULL(parse_info_->literal()); |
| DCHECK_NOT_NULL(compilation_job_.get()); |
| COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kFinalize); |
| if (trace_compiler_dispatcher_jobs_) { |
| PrintF("UnoptimizedCompileJob[%p]: Finalizing compiling\n", |
| static_cast<void*>(this)); |
| } |
| |
| Handle<Script> script(Script::cast(shared_->script()), isolate); |
| DCHECK_EQ(*parse_info_->script(), shared_->script()); |
| |
| parser_->UpdateStatistics(isolate, script); |
| parse_info_->UpdateBackgroundParseStatisticsOnMainThread(isolate); |
| parser_->HandleSourceURLComments(isolate, script); |
| |
| { |
| HandleScope scope(isolate); |
| // Internalize ast values onto the heap. |
| parse_info_->ast_value_factory()->Internalize(isolate); |
| // Allocate scope infos for the literal. |
| DeclarationScope::AllocateScopeInfos(parse_info_.get(), isolate); |
| if (compilation_job_->state() == CompilationJob::State::kFailed || |
| !Compiler::FinalizeCompilationJob(compilation_job_.release(), shared_, |
| isolate)) { |
| if (!isolate->has_pending_exception()) isolate->StackOverflow(); |
| set_status(Status::kFailed); |
| return; |
| } |
| } |
| |
| ResetDataOnMainThread(isolate); |
| set_status(Status::kDone); |
| } |
| |
| void UnoptimizedCompileJob::ReportErrorsOnMainThread(Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); |
| DCHECK_EQ(status(), Status::kHasErrorsToReport); |
| |
| if (trace_compiler_dispatcher_jobs_) { |
| PrintF("UnoptimizedCompileJob[%p]: Reporting Errors\n", |
| 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); |
| set_status(Status::kFailed); |
| } |
| |
| void UnoptimizedCompileJob::ResetDataOnMainThread(Isolate* isolate) { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| DCHECK_EQ(isolate->thread_id().ToInteger(), main_thread_id_); |
| |
| compilation_job_.reset(); |
| parser_.reset(); |
| unicode_cache_.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) { |
| if (trace_compiler_dispatcher_jobs_) { |
| PrintF("UnoptimizedCompileJob[%p]: Resetting\n", static_cast<void*>(this)); |
| } |
| |
| ResetDataOnMainThread(isolate); |
| set_status(Status::kInitial); |
| } |
| |
| double UnoptimizedCompileJob::EstimateRuntimeOfNextStepInMs() const { |
| switch (status()) { |
| case Status::kInitial: |
| return tracer_->EstimatePrepareInMs(); |
| case Status::kPrepared: |
| return tracer_->EstimateCompileInMs(parse_info_->end_position() - |
| parse_info_->start_position()); |
| case Status::kCompiled: |
| return tracer_->EstimateFinalizeInMs(); |
| |
| case Status::kHasErrorsToReport: |
| case Status::kFailed: |
| case Status::kDone: |
| return 0.0; |
| } |
| |
| UNREACHABLE(); |
| } |
| |
| void UnoptimizedCompileJob::ShortPrintOnMainThread() { |
| DCHECK_EQ(ThreadId::Current().ToInteger(), main_thread_id_); |
| DCHECK(!shared_.is_null()); |
| shared_->ShortPrint(); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |