// Copyright 2012 The Chromium Authors
// 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 <memory>
#include <sstream>
#include <utility>
#include "base/check.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);
}
constexpr 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::clamp(cpus, 1, 4);
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_ = std::make_unique<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_ = std::make_unique<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