// 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