// 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/compiler-dispatcher-job.h" #include "src/assert-scope.h" #include "src/compilation-info.h" #include "src/compiler-dispatcher/compiler-dispatcher-tracer.h" #include "src/compiler.h" #include "src/flags.h" #include "src/global-handles.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/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 CompilerDispatcherJob::CompilerDispatcherJob(Isolate* isolate, CompilerDispatcherTracer* tracer, Handle<SharedFunctionInfo> shared, size_t max_stack_size) : status_(CompileJobStatus::kInitial), isolate_(isolate), tracer_(tracer), context_(Handle<Context>::cast( isolate_->global_handles()->Create(isolate->context()))), shared_(Handle<SharedFunctionInfo>::cast( isolate_->global_handles()->Create(*shared))), max_stack_size_(max_stack_size), trace_compiler_dispatcher_jobs_(FLAG_trace_compiler_dispatcher_jobs) { DCHECK(!shared_->is_toplevel()); 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("CompilerDispatcherJob[%p] created for ", static_cast<void*>(this)); shared_->ShortPrint(); PrintF(" in initial state.\n"); } } CompilerDispatcherJob::CompilerDispatcherJob( Isolate* isolate, CompilerDispatcherTracer* tracer, Handle<Script> script, Handle<SharedFunctionInfo> shared, FunctionLiteral* literal, std::shared_ptr<Zone> parse_zone, std::shared_ptr<DeferredHandles> parse_handles, std::shared_ptr<DeferredHandles> compile_handles, size_t max_stack_size) : status_(CompileJobStatus::kAnalyzed), isolate_(isolate), tracer_(tracer), context_(Handle<Context>::cast( isolate_->global_handles()->Create(isolate->context()))), shared_(Handle<SharedFunctionInfo>::cast( isolate_->global_handles()->Create(*shared))), max_stack_size_(max_stack_size), parse_info_(new ParseInfo(shared_)), parse_zone_(parse_zone), compile_info_(new CompilationInfo(parse_info_->zone(), parse_info_.get(), Handle<JSFunction>::null())), trace_compiler_dispatcher_jobs_(FLAG_trace_compiler_dispatcher_jobs) { parse_info_->set_literal(literal); parse_info_->set_script(script); parse_info_->set_deferred_handles(parse_handles); compile_info_->set_deferred_handles(compile_handles); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p] created for ", static_cast<void*>(this)); shared_->ShortPrint(); PrintF(" in Analyzed state.\n"); } } CompilerDispatcherJob::~CompilerDispatcherJob() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status_ == CompileJobStatus::kInitial || status_ == CompileJobStatus::kDone); i::GlobalHandles::Destroy(Handle<Object>::cast(shared_).location()); i::GlobalHandles::Destroy(Handle<Object>::cast(context_).location()); } bool CompilerDispatcherJob::IsAssociatedWith( Handle<SharedFunctionInfo> shared) const { return *shared_ == *shared; } void CompilerDispatcherJob::PrepareToParseOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status() == CompileJobStatus::kInitial); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kPrepareToParse); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Preparing to parse\n", static_cast<void*>(this)); } HandleScope scope(isolate_); unicode_cache_.reset(new UnicodeCache()); Handle<Script> script(Script::cast(shared_->script()), isolate_); DCHECK(script->type() != Script::TYPE_NATIVE); Handle<String> source(String::cast(script->source()), isolate_); parse_info_.reset(new ParseInfo(isolate_->allocator())); if (source->IsExternalTwoByteString() || source->IsExternalOneByteString()) { character_stream_.reset(ScannerStream::For( source, shared_->start_position(), shared_->end_position())); } else { source = String::Flatten(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_ = Handle<String>::cast(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_->end_position() - shared_->start_position()); offset = shared_->start_position(); 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_->start_position()), byte_len); } else { MemCopy(const_cast<void*>(data), &content.ToUC16Vector().at(shared_->start_position()), byte_len); } } Handle<String> wrapper; if (source->IsOneByteRepresentation()) { ExternalOneByteString::Resource* resource = new OneByteWrapper(data, length); source_wrapper_.reset(resource); wrapper = isolate_->factory() ->NewExternalStringFromOneByte(resource) .ToHandleChecked(); } else { ExternalTwoByteString::Resource* resource = new TwoByteWrapper(data, length); source_wrapper_.reset(resource); wrapper = isolate_->factory() ->NewExternalStringFromTwoByte(resource) .ToHandleChecked(); } wrapper_ = Handle<String>::cast(isolate_->global_handles()->Create(*wrapper)); character_stream_.reset( ScannerStream::For(wrapper_, shared_->start_position() - offset, shared_->end_position() - offset)); } parse_info_->set_isolate(isolate_); parse_info_->set_character_stream(character_stream_.get()); parse_info_->set_hash_seed(isolate_->heap()->HashSeed()); parse_info_->set_is_named_expression(shared_->is_named_expression()); parse_info_->set_compiler_hints(shared_->compiler_hints()); parse_info_->set_start_position(shared_->start_position()); parse_info_->set_end_position(shared_->end_position()); parse_info_->set_unicode_cache(unicode_cache_.get()); parse_info_->set_language_mode(shared_->language_mode()); parse_info_->set_function_literal_id(shared_->function_literal_id()); parser_.reset(new Parser(parse_info_.get())); MaybeHandle<ScopeInfo> outer_scope_info; if (!shared_->outer_scope_info()->IsTheHole(isolate_) && ScopeInfo::cast(shared_->outer_scope_info())->length() > 0) { outer_scope_info = handle(ScopeInfo::cast(shared_->outer_scope_info())); } parser_->DeserializeScopeChain(parse_info_.get(), outer_scope_info); Handle<String> name(String::cast(shared_->name())); parse_info_->set_function_name( parse_info_->ast_value_factory()->GetString(name)); status_ = CompileJobStatus::kReadyToParse; } void CompilerDispatcherJob::Parse() { DCHECK(status() == CompileJobStatus::kReadyToParse); COMPILER_DISPATCHER_TRACE_SCOPE_WITH_NUM( tracer_, kParse, parse_info_->end_position() - parse_info_->start_position()); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Parsing\n", static_cast<void*>(this)); } DisallowHeapAllocation no_allocation; DisallowHandleAllocation no_handles; DisallowHandleDereference no_deref; // Nullify the Isolate temporarily so that the parser doesn't accidentally // use it. parse_info_->set_isolate(nullptr); uintptr_t stack_limit = GetCurrentStackPosition() - max_stack_size_ * KB; parser_->set_stack_limit(stack_limit); parser_->ParseOnBackground(parse_info_.get()); parse_info_->set_isolate(isolate_); status_ = CompileJobStatus::kParsed; } bool CompilerDispatcherJob::FinalizeParsingOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status() == CompileJobStatus::kParsed); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kFinalizeParsing); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Finalizing parsing\n", static_cast<void*>(this)); } if (!source_.is_null()) { i::GlobalHandles::Destroy(Handle<Object>::cast(source_).location()); source_ = Handle<String>::null(); } if (!wrapper_.is_null()) { i::GlobalHandles::Destroy(Handle<Object>::cast(wrapper_).location()); wrapper_ = Handle<String>::null(); } Handle<Script> script(Script::cast(shared_->script()), isolate_); parse_info_->set_script(script); if (parse_info_->literal() == nullptr) { parser_->ReportErrors(isolate_, script); status_ = CompileJobStatus::kFailed; } else { status_ = CompileJobStatus::kReadyToAnalyze; } parser_->UpdateStatistics(isolate_, script); DeferredHandleScope scope(isolate_); { parse_info_->ReopenHandlesInNewHandleScope(); if (!shared_->outer_scope_info()->IsTheHole(isolate_) && ScopeInfo::cast(shared_->outer_scope_info())->length() > 0) { Handle<ScopeInfo> outer_scope_info( handle(ScopeInfo::cast(shared_->outer_scope_info()))); parse_info_->set_outer_scope_info(outer_scope_info); } parse_info_->set_shared_info(shared_); // Internalize ast values on the main thread. parse_info_->ast_value_factory()->Internalize(isolate_); parser_->HandleSourceURLComments(isolate_, script); parse_info_->set_character_stream(nullptr); parse_info_->set_unicode_cache(nullptr); parser_.reset(); unicode_cache_.reset(); character_stream_.reset(); } parse_info_->set_deferred_handles(scope.Detach()); return status_ != CompileJobStatus::kFailed; } bool CompilerDispatcherJob::AnalyzeOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status() == CompileJobStatus::kReadyToAnalyze); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kAnalyze); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Analyzing\n", static_cast<void*>(this)); } compile_info_.reset(new CompilationInfo( parse_info_->zone(), parse_info_.get(), Handle<JSFunction>::null())); DeferredHandleScope scope(isolate_); { if (Compiler::Analyze(parse_info_.get())) { status_ = CompileJobStatus::kAnalyzed; } else { status_ = CompileJobStatus::kFailed; if (!isolate_->has_pending_exception()) isolate_->StackOverflow(); } } compile_info_->set_deferred_handles(scope.Detach()); return status_ != CompileJobStatus::kFailed; } bool CompilerDispatcherJob::PrepareToCompileOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status() == CompileJobStatus::kAnalyzed); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kPrepareToCompile); compile_job_.reset( Compiler::PrepareUnoptimizedCompilationJob(compile_info_.get())); if (!compile_job_.get()) { if (!isolate_->has_pending_exception()) isolate_->StackOverflow(); status_ = CompileJobStatus::kFailed; return false; } CHECK(compile_job_->can_execute_on_background_thread()); status_ = CompileJobStatus::kReadyToCompile; return true; } void CompilerDispatcherJob::Compile() { DCHECK(status() == CompileJobStatus::kReadyToCompile); COMPILER_DISPATCHER_TRACE_SCOPE_WITH_NUM( tracer_, kCompile, parse_info_->literal()->ast_node_count()); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Compiling\n", static_cast<void*>(this)); } // Disallowing of handle dereference and heap access dealt with in // CompilationJob::ExecuteJob. uintptr_t stack_limit = GetCurrentStackPosition() - max_stack_size_ * KB; compile_job_->set_stack_limit(stack_limit); CompilationJob::Status status = compile_job_->ExecuteJob(); USE(status); // Always transition to kCompiled - errors will be reported by // FinalizeCompilingOnMainThread. status_ = CompileJobStatus::kCompiled; } bool CompilerDispatcherJob::FinalizeCompilingOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); DCHECK(status() == CompileJobStatus::kCompiled); COMPILER_DISPATCHER_TRACE_SCOPE(tracer_, kFinalizeCompiling); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Finalizing compiling\n", static_cast<void*>(this)); } { HandleScope scope(isolate_); if (compile_job_->state() == CompilationJob::State::kFailed || !Compiler::FinalizeCompilationJob(compile_job_.release())) { if (!isolate_->has_pending_exception()) isolate_->StackOverflow(); status_ = CompileJobStatus::kFailed; return false; } } compile_job_.reset(); compile_info_.reset(); parse_zone_.reset(); parse_info_.reset(); status_ = CompileJobStatus::kDone; return true; } void CompilerDispatcherJob::ResetOnMainThread() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); if (trace_compiler_dispatcher_jobs_) { PrintF("CompilerDispatcherJob[%p]: Resetting\n", static_cast<void*>(this)); } compile_job_.reset(); compile_info_.reset(); parse_zone_.reset(); parser_.reset(); unicode_cache_.reset(); character_stream_.reset(); parse_info_.reset(); if (!source_.is_null()) { i::GlobalHandles::Destroy(Handle<Object>::cast(source_).location()); source_ = Handle<String>::null(); } if (!wrapper_.is_null()) { i::GlobalHandles::Destroy(Handle<Object>::cast(wrapper_).location()); wrapper_ = Handle<String>::null(); } status_ = CompileJobStatus::kInitial; } double CompilerDispatcherJob::EstimateRuntimeOfNextStepInMs() const { switch (status_) { case CompileJobStatus::kInitial: return tracer_->EstimatePrepareToParseInMs(); case CompileJobStatus::kReadyToParse: return tracer_->EstimateParseInMs(parse_info_->end_position() - parse_info_->start_position()); case CompileJobStatus::kParsed: return tracer_->EstimateFinalizeParsingInMs(); case CompileJobStatus::kReadyToAnalyze: return tracer_->EstimateAnalyzeInMs(); case CompileJobStatus::kAnalyzed: return tracer_->EstimatePrepareToCompileInMs(); case CompileJobStatus::kReadyToCompile: return tracer_->EstimateCompileInMs( parse_info_->literal()->ast_node_count()); case CompileJobStatus::kCompiled: return tracer_->EstimateFinalizeCompilingInMs(); case CompileJobStatus::kFailed: case CompileJobStatus::kDone: return 0.0; } UNREACHABLE(); return 0.0; } void CompilerDispatcherJob::ShortPrint() { DCHECK(ThreadId::Current().Equals(isolate_->thread_id())); shared_->ShortPrint(); } } // namespace internal } // namespace v8