| // 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_translate_thread.h" |
| |
| #include <stddef.h> |
| |
| #include <iterator> |
| #include <sstream> |
| |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "components/nacl/renderer/plugin/plugin.h" |
| #include "components/nacl/renderer/plugin/plugin_error.h" |
| #include "content/public/common/sandbox_init.h" |
| #include "ppapi/c/ppb_file_io.h" |
| #include "ppapi/cpp/var.h" |
| #include "ppapi/proxy/ppapi_messages.h" |
| |
| namespace plugin { |
| namespace { |
| |
| template <typename Val> |
| std::string MakeCommandLineArg(const char* key, const Val val) { |
| std::stringstream ss; |
| ss << key << val; |
| return ss.str(); |
| } |
| |
| void GetLlcCommandLine(std::vector<std::string>* args, |
| size_t obj_files_size, |
| int32_t opt_level, |
| bool is_debug, |
| const std::string& architecture_attributes) { |
| // TODO(dschuff): This CL override is ugly. Change llc to default to |
| // using the number of modules specified in the first param, and |
| // ignore multiple uses of -split-module |
| args->push_back(MakeCommandLineArg("-split-module=", obj_files_size)); |
| args->push_back(MakeCommandLineArg("-O", opt_level)); |
| if (is_debug) |
| args->push_back("-bitcode-format=llvm"); |
| if (!architecture_attributes.empty()) |
| args->push_back("-mattr=" + architecture_attributes); |
| } |
| |
| void GetSubzeroCommandLine(std::vector<std::string>* args, |
| int32_t opt_level, |
| bool is_debug, |
| const std::string& architecture_attributes) { |
| args->push_back(MakeCommandLineArg("-O", opt_level)); |
| DCHECK(!is_debug); |
| // TODO(stichnot): enable this once the mattr flag formatting is |
| // compatible: https://code.google.com/p/nativeclient/issues/detail?id=4132 |
| // if (!architecture_attributes.empty()) |
| // args->push_back("-mattr=" + architecture_attributes); |
| } |
| |
| } // namespace |
| |
| PnaclTranslateThread::PnaclTranslateThread() |
| : compiler_subprocess_(NULL), |
| ld_subprocess_(NULL), |
| compiler_subprocess_active_(false), |
| ld_subprocess_active_(false), |
| buffer_cond_(&cond_mu_), |
| done_(false), |
| compile_time_(0), |
| obj_files_(NULL), |
| num_threads_(0), |
| nexe_file_(NULL), |
| coordinator_error_info_(NULL), |
| coordinator_(NULL) {} |
| |
| void PnaclTranslateThread::SetupState( |
| const pp::CompletionCallback& finish_callback, |
| NaClSubprocess* compiler_subprocess, |
| NaClSubprocess* ld_subprocess, |
| std::vector<base::File>* obj_files, |
| int num_threads, |
| base::File* nexe_file, |
| ErrorInfo* error_info, |
| PP_PNaClOptions* pnacl_options, |
| const std::string& architecture_attributes, |
| PnaclCoordinator* coordinator) { |
| compiler_subprocess_ = compiler_subprocess; |
| ld_subprocess_ = ld_subprocess; |
| obj_files_ = obj_files; |
| num_threads_ = num_threads; |
| nexe_file_ = nexe_file; |
| coordinator_error_info_ = error_info; |
| pnacl_options_ = pnacl_options; |
| architecture_attributes_ = architecture_attributes; |
| coordinator_ = coordinator; |
| |
| report_translate_finished_ = finish_callback; |
| } |
| |
| void PnaclTranslateThread::RunCompile( |
| const pp::CompletionCallback& compile_finished_callback) { |
| DCHECK(started()); |
| DCHECK(compiler_subprocess_->service_runtime()); |
| compiler_subprocess_active_ = true; |
| |
| // Take ownership of this IPC channel to make sure that it does not get |
| // freed on the child thread when the child thread calls Shutdown(). |
| compiler_channel_ = |
| compiler_subprocess_->service_runtime()->TakeTranslatorChannel(); |
| // compiler_channel_ is a IPC::SyncChannel, which is not thread-safe and |
| // cannot be used directly by the child thread, so create a |
| // SyncMessageFilter which can be used by the child thread. |
| compiler_channel_filter_ = compiler_channel_->CreateSyncMessageFilter(); |
| |
| compile_finished_callback_ = compile_finished_callback; |
| translate_thread_.reset(new CompileThread(this)); |
| translate_thread_->Start(); |
| } |
| |
| void PnaclTranslateThread::RunLink() { |
| DCHECK(started()); |
| DCHECK(ld_subprocess_->service_runtime()); |
| ld_subprocess_active_ = true; |
| |
| // Take ownership of this IPC channel to make sure that it does not get |
| // freed on the child thread when the child thread calls Shutdown(). |
| ld_channel_ = ld_subprocess_->service_runtime()->TakeTranslatorChannel(); |
| // ld_channel_ is a IPC::SyncChannel, which is not thread-safe and cannot be |
| // used directly by the child thread, so create a SyncMessageFilter which |
| // can be used by the child thread. |
| ld_channel_filter_ = ld_channel_->CreateSyncMessageFilter(); |
| |
| // Tear down the previous thread. |
| translate_thread_->Join(); |
| translate_thread_.reset(new LinkThread(this)); |
| translate_thread_->Start(); |
| } |
| |
| // Called from main thread to send bytes to the translator. |
| void PnaclTranslateThread::PutBytes(const void* bytes, int32_t count) { |
| CHECK(bytes != NULL); |
| base::AutoLock lock(cond_mu_); |
| data_buffers_.push_back(std::string()); |
| data_buffers_.back().insert(data_buffers_.back().end(), |
| static_cast<const char*>(bytes), |
| static_cast<const char*>(bytes) + count); |
| buffer_cond_.Signal(); |
| } |
| |
| void PnaclTranslateThread::EndStream() { |
| base::AutoLock lock(cond_mu_); |
| done_ = true; |
| buffer_cond_.Signal(); |
| } |
| |
| ppapi::proxy::SerializedHandle PnaclTranslateThread::GetHandleForSubprocess( |
| base::File* file, |
| int32_t open_flags) { |
| DCHECK(file->IsValid()); |
| IPC::PlatformFileForTransit file_for_transit = |
| IPC::GetPlatformFileForTransit(file->GetPlatformFile(), false); |
| |
| // Using 0 disables any use of quota enforcement for this file handle. |
| PP_Resource file_io = 0; |
| |
| ppapi::proxy::SerializedHandle handle; |
| handle.set_file_handle(file_for_transit, open_flags, file_io); |
| return handle; |
| } |
| |
| void PnaclTranslateThread::CompileThread::Run() { |
| pnacl_translate_thread_->DoCompile(); |
| } |
| |
| void PnaclTranslateThread::DoCompile() { |
| { |
| base::AutoLock lock(subprocess_mu_); |
| // If the main thread asked us to exit in between starting the thread |
| // and now, just leave now. |
| if (!compiler_subprocess_active_) |
| return; |
| } |
| |
| std::vector<ppapi::proxy::SerializedHandle> compiler_output_files; |
| for (base::File& obj_file : *obj_files_) { |
| compiler_output_files.push_back( |
| GetHandleForSubprocess(&obj_file, PP_FILEOPENFLAG_WRITE)); |
| } |
| |
| pp::Core* core = pp::Module::Get()->core(); |
| base::TimeTicks do_compile_start_time = base::TimeTicks::Now(); |
| |
| std::vector<std::string> args; |
| if (pnacl_options_->use_subzero) { |
| GetSubzeroCommandLine(&args, pnacl_options_->opt_level, |
| PP_ToBool(pnacl_options_->is_debug), |
| architecture_attributes_); |
| } else { |
| GetLlcCommandLine(&args, obj_files_->size(), |
| pnacl_options_->opt_level, |
| PP_ToBool(pnacl_options_->is_debug), |
| architecture_attributes_); |
| } |
| |
| bool success = false; |
| std::string error_str; |
| if (!compiler_channel_filter_->Send( |
| new PpapiMsg_PnaclTranslatorCompileInit( |
| num_threads_, compiler_output_files, args, &success, &error_str))) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, |
| "Compile stream init failed: " |
| "reply not received from PNaCl translator " |
| "(it probably crashed)"); |
| return; |
| } |
| if (!success) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, |
| std::string("Stream init failed: ") + error_str); |
| return; |
| } |
| |
| // llc process is started. |
| while(!done_ || data_buffers_.size() > 0) { |
| cond_mu_.Acquire(); |
| while(!done_ && data_buffers_.size() == 0) { |
| buffer_cond_.Wait(); |
| } |
| if (data_buffers_.size() > 0) { |
| std::string data; |
| data.swap(data_buffers_.front()); |
| data_buffers_.pop_front(); |
| cond_mu_.Release(); |
| |
| if (!compiler_channel_filter_->Send( |
| new PpapiMsg_PnaclTranslatorCompileChunk(data, &success))) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, |
| "Compile stream chunk failed: " |
| "reply not received from PNaCl translator " |
| "(it probably crashed)"); |
| return; |
| } |
| if (!success) { |
| // If the error was reported by the translator, then we fall through |
| // and call PpapiMsg_PnaclTranslatorCompileEnd, which returns a string |
| // describing the error, which we can then send to the Javascript |
| // console. |
| break; |
| } |
| core->CallOnMainThread( |
| 0, |
| coordinator_->GetCompileProgressCallback(data.size()), |
| PP_OK); |
| } else { |
| cond_mu_.Release(); |
| } |
| } |
| // Finish llc. |
| if (!compiler_channel_filter_->Send( |
| new PpapiMsg_PnaclTranslatorCompileEnd(&success, &error_str))) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, |
| "Compile stream end failed: " |
| "reply not received from PNaCl translator " |
| "(it probably crashed)"); |
| return; |
| } |
| if (!success) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, error_str); |
| return; |
| } |
| compile_time_ = |
| (base::TimeTicks::Now() - do_compile_start_time).InMicroseconds(); |
| nacl::PPBNaClPrivate::LogTranslateTime("NaCl.Perf.PNaClLoadTime.CompileTime", |
| compile_time_); |
| nacl::PPBNaClPrivate::LogTranslateTime( |
| pnacl_options_->use_subzero |
| ? "NaCl.Perf.PNaClLoadTime.CompileTime.Subzero" |
| : "NaCl.Perf.PNaClLoadTime.CompileTime.LLC", |
| compile_time_); |
| |
| // Shut down the compiler subprocess. |
| { |
| base::AutoLock lock(subprocess_mu_); |
| compiler_subprocess_active_ = false; |
| } |
| |
| core->CallOnMainThread(0, compile_finished_callback_, PP_OK); |
| } |
| |
| void PnaclTranslateThread::LinkThread::Run() { |
| pnacl_translate_thread_->DoLink(); |
| } |
| |
| void PnaclTranslateThread::DoLink() { |
| { |
| base::AutoLock lock(subprocess_mu_); |
| // If the main thread asked us to exit in between starting the thread |
| // and now, just leave now. |
| if (!ld_subprocess_active_) |
| return; |
| } |
| |
| // Reset object files for reading first. We do this before duplicating |
| // handles/FDs to prevent any handle/FD leaks in case any of the Seek() |
| // calls fail. |
| for (base::File& obj_file : *obj_files_) { |
| if (obj_file.Seek(base::File::FROM_BEGIN, 0) != 0) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LD_SETUP, |
| "Link process could not reset object file"); |
| return; |
| } |
| } |
| |
| ppapi::proxy::SerializedHandle nexe_file = |
| GetHandleForSubprocess(nexe_file_, PP_FILEOPENFLAG_WRITE); |
| std::vector<ppapi::proxy::SerializedHandle> ld_input_files; |
| for (base::File& obj_file : *obj_files_) { |
| ld_input_files.push_back( |
| GetHandleForSubprocess(&obj_file, PP_FILEOPENFLAG_READ)); |
| } |
| |
| base::TimeTicks link_start_time = base::TimeTicks::Now(); |
| bool success = false; |
| bool sent = ld_channel_filter_->Send( |
| new PpapiMsg_PnaclTranslatorLink(ld_input_files, nexe_file, &success)); |
| if (!sent) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LD_INTERNAL, |
| "link failed: reply not received from linker."); |
| return; |
| } |
| if (!success) { |
| TranslateFailed(PP_NACL_ERROR_PNACL_LD_INTERNAL, |
| "link failed: linker returned failure status."); |
| return; |
| } |
| |
| nacl::PPBNaClPrivate::LogTranslateTime( |
| "NaCl.Perf.PNaClLoadTime.LinkTime", |
| (base::TimeTicks::Now() - link_start_time).InMicroseconds()); |
| |
| // Shut down the ld subprocess. |
| { |
| base::AutoLock lock(subprocess_mu_); |
| ld_subprocess_active_ = false; |
| } |
| |
| pp::Core* core = pp::Module::Get()->core(); |
| core->CallOnMainThread(0, report_translate_finished_, PP_OK); |
| } |
| |
| void PnaclTranslateThread::TranslateFailed( |
| PP_NaClError err_code, |
| const std::string& error_string) { |
| pp::Core* core = pp::Module::Get()->core(); |
| if (coordinator_error_info_->message().empty()) { |
| // Only use our message if one hasn't already been set by the coordinator |
| // (e.g. pexe load failed). |
| coordinator_error_info_->SetReport(err_code, |
| std::string("PnaclCoordinator: ") + |
| error_string); |
| } |
| core->CallOnMainThread(0, report_translate_finished_, PP_ERROR_FAILED); |
| } |
| |
| void PnaclTranslateThread::AbortSubprocesses() { |
| { |
| base::AutoLock lock(subprocess_mu_); |
| if (compiler_subprocess_ != NULL && compiler_subprocess_active_) { |
| // We only run the service_runtime's Shutdown and do not run the |
| // NaClSubprocess Shutdown, which would otherwise nullify some |
| // pointers that could still be in use (srpc_client, etc.). |
| compiler_subprocess_->service_runtime()->Shutdown(); |
| compiler_subprocess_active_ = false; |
| } |
| if (ld_subprocess_ != NULL && ld_subprocess_active_) { |
| ld_subprocess_->service_runtime()->Shutdown(); |
| ld_subprocess_active_ = false; |
| } |
| } |
| base::AutoLock lock(cond_mu_); |
| done_ = true; |
| // Free all buffered bitcode chunks |
| data_buffers_.clear(); |
| buffer_cond_.Signal(); |
| } |
| |
| PnaclTranslateThread::~PnaclTranslateThread() { |
| AbortSubprocesses(); |
| if (translate_thread_) |
| translate_thread_->Join(); |
| } |
| |
| } // namespace plugin |