chromium/components/nacl/renderer/plugin/pnacl_translate_thread.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "components/nacl/renderer/plugin/pnacl_translate_thread.h"

#include <stddef.h>

#include <iterator>
#include <memory>
#include <sstream>

#include "base/check.h"
#include "base/time/time.h"
#include "components/nacl/renderer/plugin/plugin.h"
#include "components/nacl/renderer/plugin/plugin_error.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_(nullptr),
      ld_subprocess_(nullptr),
      compiler_subprocess_active_(false),
      ld_subprocess_active_(false),
      buffer_cond_(&cond_mu_),
      done_(false),
      compile_time_(0),
      obj_files_(nullptr),
      num_threads_(0),
      nexe_file_(nullptr),
      coordinator_error_info_(nullptr),
      coordinator_(nullptr) {}

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_ = std::make_unique<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_ = std::make_unique<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);
  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_ && 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_ && 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