text: // 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;
      compiler_subprocess_->Shutdown();
    }
  
    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;
      ld_subprocess_->Shutdown();
    }
  
    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
  