| // Copyright (c) 2012 The Chromium 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 "components/nacl/renderer/plugin/pnacl_coordinator.h" |
| |
| #include <algorithm> |
| #include <sstream> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "components/nacl/renderer/plugin/plugin.h" |
| #include "components/nacl/renderer/plugin/plugin_error.h" |
| #include "components/nacl/renderer/plugin/pnacl_translate_thread.h" |
| #include "components/nacl/renderer/plugin/service_runtime.h" |
| #include "ppapi/c/pp_bool.h" |
| #include "ppapi/c/pp_errors.h" |
| |
| namespace plugin { |
| |
| namespace { |
| |
| std::string GetArchitectureAttributes(Plugin* plugin) { |
| pp::Var attrs_var(pp::PASS_REF, |
| nacl::PPBNaClPrivate::GetCpuFeatureAttrs()); |
| return attrs_var.AsString(); |
| } |
| |
| void DidCacheHit(void* user_data, PP_FileHandle nexe_file_handle) { |
| PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data); |
| coordinator->BitcodeStreamCacheHit(nexe_file_handle); |
| } |
| |
| void DidCacheMiss(void* user_data, int64_t expected_pexe_size, |
| PP_FileHandle temp_nexe_file) { |
| PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data); |
| coordinator->BitcodeStreamCacheMiss(expected_pexe_size, |
| temp_nexe_file); |
| } |
| |
| void DidStreamData(void* user_data, const void* stream_data, int32_t length) { |
| PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data); |
| coordinator->BitcodeStreamGotData(stream_data, length); |
| } |
| |
| void DidFinishStream(void* user_data, int32_t pp_error) { |
| PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data); |
| coordinator->BitcodeStreamDidFinish(pp_error); |
| } |
| |
| PPP_PexeStreamHandler kPexeStreamHandler = { |
| &DidCacheHit, |
| &DidCacheMiss, |
| &DidStreamData, |
| &DidFinishStream |
| }; |
| |
| } // namespace |
| |
| PnaclCoordinator* PnaclCoordinator::BitcodeToNative( |
| Plugin* plugin, |
| const std::string& pexe_url, |
| const PP_PNaClOptions& pnacl_options, |
| const pp::CompletionCallback& translate_notify_callback) { |
| PnaclCoordinator* coordinator = |
| new PnaclCoordinator(plugin, pexe_url, |
| pnacl_options, |
| translate_notify_callback); |
| |
| nacl::PPBNaClPrivate::SetPNaClStartTime(plugin->pp_instance()); |
| int cpus = nacl::PPBNaClPrivate::GetNumberOfProcessors(); |
| coordinator->num_threads_ = std::min(4, std::max(1, cpus)); |
| if (pnacl_options.use_subzero) { |
| coordinator->split_module_count_ = 1; |
| } else { |
| coordinator->split_module_count_ = coordinator->num_threads_; |
| } |
| // First start a network request for the pexe, to tickle the component |
| // updater's On-Demand resource throttler, and to get Last-Modified/ETag |
| // cache information. We can cancel the request later if there's |
| // a bitcode->nexe cache hit. |
| coordinator->OpenBitcodeStream(); |
| return coordinator; |
| } |
| |
| PnaclCoordinator::PnaclCoordinator( |
| Plugin* plugin, |
| const std::string& pexe_url, |
| const PP_PNaClOptions& pnacl_options, |
| const pp::CompletionCallback& translate_notify_callback) |
| : translate_finish_error_(PP_OK), |
| plugin_(plugin), |
| translate_notify_callback_(translate_notify_callback), |
| translation_finished_reported_(false), |
| pexe_url_(pexe_url), |
| pnacl_options_(pnacl_options), |
| architecture_attributes_(GetArchitectureAttributes(plugin)), |
| split_module_count_(0), |
| num_threads_(0), |
| error_already_reported_(false), |
| pexe_size_(0), |
| pexe_bytes_compiled_(0), |
| expected_pexe_size_(-1) { |
| callback_factory_.Initialize(this); |
| } |
| |
| PnaclCoordinator::~PnaclCoordinator() { |
| // Stopping the translate thread will cause the translate thread to try to |
| // run translation_complete_callback_ on the main thread. This destructor is |
| // running from the main thread, and by the time it exits, callback_factory_ |
| // will have been destroyed. This will result in the cancellation of |
| // translation_complete_callback_, so no notification will be delivered. |
| if (translate_thread_.get() != NULL) |
| translate_thread_->AbortSubprocesses(); |
| if (!translation_finished_reported_) { |
| nacl::PPBNaClPrivate::ReportTranslationFinished( |
| plugin_->pp_instance(), PP_FALSE, pnacl_options_.opt_level, |
| pnacl_options_.use_subzero, 0, 0, 0); |
| } |
| // Force deleting the translate_thread now. It must be deleted |
| // before any scoped_* fields hanging off of PnaclCoordinator |
| // since the thread may be accessing those fields. |
| // It will also be accessing obj_files_. |
| translate_thread_.reset(NULL); |
| } |
| |
| PP_FileHandle PnaclCoordinator::TakeTranslatedFileHandle() { |
| DCHECK(temp_nexe_file_.IsValid()); |
| return temp_nexe_file_.TakePlatformFile(); |
| } |
| |
| void PnaclCoordinator::ReportNonPpapiError(PP_NaClError err_code, |
| const std::string& message) { |
| ErrorInfo error_info; |
| error_info.SetReport(err_code, message); |
| plugin_->ReportLoadError(error_info); |
| ExitWithError(); |
| } |
| |
| void PnaclCoordinator::ExitWithError() { |
| // Free all the intermediate callbacks we ever created. |
| // Note: this doesn't *cancel* the callbacks from the factories attached |
| // to the various helper classes (e.g., pnacl_resources). Thus, those |
| // callbacks may still run asynchronously. We let those run but ignore |
| // any other errors they may generate so that they do not end up running |
| // translate_notify_callback_, which has already been freed. |
| callback_factory_.CancelAll(); |
| if (!error_already_reported_) { |
| error_already_reported_ = true; |
| translation_finished_reported_ = true; |
| nacl::PPBNaClPrivate::ReportTranslationFinished( |
| plugin_->pp_instance(), PP_FALSE, pnacl_options_.opt_level, |
| pnacl_options_.use_subzero, 0, 0, 0); |
| translate_notify_callback_.Run(PP_ERROR_FAILED); |
| } |
| } |
| |
| // Signal that Pnacl translation completed normally. |
| void PnaclCoordinator::TranslateFinished(int32_t pp_error) { |
| // Bail out if there was an earlier error (e.g., pexe load failure), |
| // or if there is an error from the translation thread. |
| if (translate_finish_error_ != PP_OK || pp_error != PP_OK) { |
| plugin_->ReportLoadError(error_info_); |
| ExitWithError(); |
| return; |
| } |
| |
| // Send out one last progress event, to finish up the progress events |
| // that were delayed (see the delay inserted in BitcodeGotCompiled). |
| if (expected_pexe_size_ != -1) { |
| pexe_bytes_compiled_ = expected_pexe_size_; |
| nacl::PPBNaClPrivate::DispatchEvent(plugin_->pp_instance(), |
| PP_NACL_EVENT_PROGRESS, |
| pexe_url_.c_str(), |
| PP_TRUE, |
| pexe_bytes_compiled_, |
| expected_pexe_size_); |
| } |
| int64_t nexe_size = temp_nexe_file_.GetLength(); |
| // The nexe is written to the temp_nexe_file_. We must reset the file |
| // pointer to be able to read it again from the beginning. |
| temp_nexe_file_.Seek(base::File::FROM_BEGIN, 0); |
| |
| // Report to the browser that translation finished. The browser will take |
| // care of storing the nexe in the cache. |
| translation_finished_reported_ = true; |
| nacl::PPBNaClPrivate::ReportTranslationFinished( |
| plugin_->pp_instance(), PP_TRUE, pnacl_options_.opt_level, |
| pnacl_options_.use_subzero, nexe_size, pexe_size_, |
| translate_thread_->GetCompileTime()); |
| |
| NexeReadDidOpen(); |
| } |
| |
| void PnaclCoordinator::NexeReadDidOpen() { |
| if (!temp_nexe_file_.IsValid()) { |
| ReportNonPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_OTHER, |
| "Failed to open translated nexe."); |
| return; |
| } |
| |
| translate_notify_callback_.Run(PP_OK); |
| } |
| |
| void PnaclCoordinator::OpenBitcodeStream() { |
| // Even though we haven't started downloading, create the translation |
| // thread object immediately. This ensures that any pieces of the file |
| // that get downloaded before the compilation thread is accepting |
| // SRPCs won't get dropped. |
| translate_thread_.reset(new PnaclTranslateThread()); |
| if (translate_thread_ == NULL) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_THREAD_CREATE, |
| "PnaclCoordinator: could not allocate translation thread."); |
| return; |
| } |
| |
| nacl::PPBNaClPrivate::StreamPexe( |
| plugin_->pp_instance(), pexe_url_.c_str(), pnacl_options_.opt_level, |
| pnacl_options_.use_subzero, &kPexeStreamHandler, this); |
| } |
| |
| void PnaclCoordinator::BitcodeStreamCacheHit(PP_FileHandle handle) { |
| if (handle == PP_kInvalidFileHandle) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_CREATE_TEMP, |
| std::string( |
| "PnaclCoordinator: Got bad temp file handle from GetNexeFd")); |
| BitcodeStreamDidFinish(PP_ERROR_FAILED); |
| return; |
| } |
| temp_nexe_file_ = base::File(handle); |
| NexeReadDidOpen(); |
| } |
| |
| void PnaclCoordinator::BitcodeStreamCacheMiss(int64_t expected_pexe_size, |
| PP_FileHandle nexe_handle) { |
| // IMPORTANT: Make sure that PnaclResources::StartLoad() is only |
| // called after you receive a response to a request for a .pexe file. |
| // |
| // The component updater's resource throttles + OnDemand update/install |
| // should block the URL request until the compiler is present. Now we |
| // can load the resources (e.g. llc and ld nexes). |
| resources_.reset(new PnaclResources(plugin_, |
| PP_ToBool(pnacl_options_.use_subzero))); |
| CHECK(resources_ != NULL); |
| |
| // The first step of loading resources: read the resource info file. |
| if (!resources_->ReadResourceInfo()) { |
| ExitWithError(); |
| return; |
| } |
| |
| // Second step of loading resources: call StartLoad to load pnacl-llc |
| // and pnacl-ld, based on the filenames found in the resource info file. |
| if (!resources_->StartLoad()) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_RESOURCE_FETCH, |
| std::string("The Portable Native Client (pnacl) component is not " |
| "installed. Please consult chrome://components for more " |
| "information.")); |
| return; |
| } |
| |
| expected_pexe_size_ = expected_pexe_size; |
| |
| for (int i = 0; i < split_module_count_; i++) { |
| base::File temp_file( |
| nacl::PPBNaClPrivate::CreateTemporaryFile(plugin_->pp_instance())); |
| if (!temp_file.IsValid()) { |
| ReportNonPpapiError(PP_NACL_ERROR_PNACL_CREATE_TEMP, |
| "Failed to open scratch object file."); |
| return; |
| } |
| obj_files_.push_back(std::move(temp_file)); |
| } |
| |
| temp_nexe_file_ = base::File(nexe_handle); |
| // Open the nexe file for connecting ld and sel_ldr. |
| // Start translation when done with this last step of setup! |
| if (!temp_nexe_file_.IsValid()) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_CREATE_TEMP, |
| std::string( |
| "PnaclCoordinator: Got bad temp file handle from writing nexe")); |
| return; |
| } |
| LoadCompiler(); |
| } |
| |
| void PnaclCoordinator::BitcodeStreamGotData(const void* data, int32_t length) { |
| DCHECK(translate_thread_.get()); |
| |
| translate_thread_->PutBytes(data, length); |
| if (data && length > 0) |
| pexe_size_ += length; |
| } |
| |
| void PnaclCoordinator::BitcodeStreamDidFinish(int32_t pp_error) { |
| if (pp_error != PP_OK) { |
| // Defer reporting the error and cleanup until after the translation |
| // thread returns, because it may be accessing the coordinator's |
| // objects or writing to the files. |
| translate_finish_error_ = pp_error; |
| if (pp_error == PP_ERROR_ABORTED) { |
| error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_ABORTED, |
| "PnaclCoordinator: pexe load failed (aborted)."); |
| } |
| if (pp_error == PP_ERROR_NOACCESS) { |
| error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_NOACCESS, |
| "PnaclCoordinator: pexe load failed (no access)."); |
| } else { |
| std::stringstream ss; |
| ss << "PnaclCoordinator: pexe load failed (pp_error=" << pp_error << ")."; |
| error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_OTHER, ss.str()); |
| } |
| |
| if (translate_thread_->started()) |
| translate_thread_->AbortSubprocesses(); |
| else |
| TranslateFinished(pp_error); |
| } else { |
| // Compare download completion pct (100% now), to compile completion pct. |
| nacl::PPBNaClPrivate::LogBytesCompiledVsDownloaded( |
| pnacl_options_.use_subzero, pexe_bytes_compiled_, pexe_size_); |
| translate_thread_->EndStream(); |
| } |
| } |
| |
| void PnaclCoordinator::BitcodeGotCompiled(int32_t pp_error, |
| int64_t bytes_compiled) { |
| DCHECK(pp_error == PP_OK); |
| pexe_bytes_compiled_ += bytes_compiled; |
| // Hold off reporting the last few bytes of progress, since we don't know |
| // when they are actually completely compiled. "bytes_compiled" only means |
| // that bytes were sent to the compiler. |
| if (expected_pexe_size_ != -1) { |
| if (!ShouldDelayProgressEvent()) { |
| nacl::PPBNaClPrivate::DispatchEvent(plugin_->pp_instance(), |
| PP_NACL_EVENT_PROGRESS, |
| pexe_url_.c_str(), |
| PP_TRUE, |
| pexe_bytes_compiled_, |
| expected_pexe_size_); |
| } |
| } else { |
| nacl::PPBNaClPrivate::DispatchEvent(plugin_->pp_instance(), |
| PP_NACL_EVENT_PROGRESS, |
| pexe_url_.c_str(), |
| PP_FALSE, |
| pexe_bytes_compiled_, |
| expected_pexe_size_); |
| } |
| } |
| |
| pp::CompletionCallback PnaclCoordinator::GetCompileProgressCallback( |
| int64_t bytes_compiled) { |
| return callback_factory_.NewCallback(&PnaclCoordinator::BitcodeGotCompiled, |
| bytes_compiled); |
| } |
| |
| void PnaclCoordinator::LoadCompiler() { |
| base::TimeTicks compiler_load_start_time = base::TimeTicks::Now(); |
| pp::CompletionCallback load_finished = callback_factory_.NewCallback( |
| &PnaclCoordinator::RunCompile, compiler_load_start_time); |
| PnaclResources::ResourceType compiler_type = pnacl_options_.use_subzero |
| ? PnaclResources::SUBZERO |
| : PnaclResources::LLC; |
| // Transfer file_info ownership to the sel_ldr launcher. |
| PP_NaClFileInfo file_info = resources_->TakeFileInfo(compiler_type); |
| const std::string& url = resources_->GetUrl(compiler_type); |
| plugin_->LoadHelperNaClModule(url, file_info, &compiler_subprocess_, |
| load_finished); |
| } |
| |
| void PnaclCoordinator::RunCompile(int32_t pp_error, |
| base::TimeTicks compiler_load_start_time) { |
| if (pp_error != PP_OK) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_LLC_SETUP, |
| "PnaclCoordinator: Compiler process could not be created."); |
| return; |
| } |
| int64_t compiler_load_time_total = |
| (base::TimeTicks::Now() - compiler_load_start_time).InMicroseconds(); |
| nacl::PPBNaClPrivate::LogTranslateTime("NaCl.Perf.PNaClLoadTime.LoadCompiler", |
| compiler_load_time_total); |
| nacl::PPBNaClPrivate::LogTranslateTime( |
| pnacl_options_.use_subzero |
| ? "NaCl.Perf.PNaClLoadTime.LoadCompiler.Subzero" |
| : "NaCl.Perf.PNaClLoadTime.LoadCompiler.LLC", |
| compiler_load_time_total); |
| |
| // Invoke llc followed by ld off the main thread. This allows use of |
| // blocking RPCs that would otherwise block the JavaScript main thread. |
| pp::CompletionCallback report_translate_finished = |
| callback_factory_.NewCallback(&PnaclCoordinator::TranslateFinished); |
| pp::CompletionCallback compile_finished = |
| callback_factory_.NewCallback(&PnaclCoordinator::LoadLinker); |
| CHECK(translate_thread_ != NULL); |
| translate_thread_->SetupState( |
| report_translate_finished, &compiler_subprocess_, &ld_subprocess_, |
| &obj_files_, num_threads_, &temp_nexe_file_, |
| &error_info_, &pnacl_options_, architecture_attributes_, this); |
| translate_thread_->RunCompile(compile_finished); |
| } |
| |
| void PnaclCoordinator::LoadLinker(int32_t pp_error) { |
| // Errors in the previous step would have skipped to TranslateFinished |
| // so we only expect PP_OK here. |
| DCHECK(pp_error == PP_OK); |
| if (pp_error != PP_OK) { |
| return; |
| } |
| ErrorInfo error_info; |
| base::TimeTicks ld_load_start_time = base::TimeTicks::Now(); |
| pp::CompletionCallback load_finished = callback_factory_.NewCallback( |
| &PnaclCoordinator::RunLink, ld_load_start_time); |
| // Transfer file_info ownership to the sel_ldr launcher. |
| PP_NaClFileInfo ld_file_info = resources_->TakeFileInfo(PnaclResources::LD); |
| plugin_->LoadHelperNaClModule(resources_->GetUrl(PnaclResources::LD), |
| ld_file_info, &ld_subprocess_, load_finished); |
| } |
| |
| void PnaclCoordinator::RunLink(int32_t pp_error, |
| base::TimeTicks ld_load_start_time) { |
| if (pp_error != PP_OK) { |
| ReportNonPpapiError( |
| PP_NACL_ERROR_PNACL_LD_SETUP, |
| "PnaclCoordinator: Linker process could not be created."); |
| return; |
| } |
| nacl::PPBNaClPrivate::LogTranslateTime( |
| "NaCl.Perf.PNaClLoadTime.LoadLinker", |
| (base::TimeTicks::Now() - ld_load_start_time).InMicroseconds()); |
| translate_thread_->RunLink(); |
| } |
| |
| } // namespace plugin |