1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ppapi/native_client/src/trusted/plugin/pnacl_coordinator.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "native_client/src/include/portability_io.h"
11#include "native_client/src/shared/platform/nacl_check.h"
12#include "native_client/src/trusted/service_runtime/include/sys/stat.h"
13
14#include "ppapi/c/pp_bool.h"
15#include "ppapi/c/pp_errors.h"
16#include "ppapi/c/private/ppb_uma_private.h"
17
18#include "ppapi/native_client/src/trusted/plugin/plugin.h"
19#include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
20#include "ppapi/native_client/src/trusted/plugin/pnacl_translate_thread.h"
21#include "ppapi/native_client/src/trusted/plugin/service_runtime.h"
22#include "ppapi/native_client/src/trusted/plugin/temporary_file.h"
23
24namespace plugin {
25
26namespace {
27
28const int32_t kSizeKBMin = 1;
29const int32_t kSizeKBMax = 512*1024;       // very large .pexe / .nexe.
30const uint32_t kSizeKBBuckets = 100;
31
32const int32_t kRatioMin = 10;
33const int32_t kRatioMax = 10*100;          // max of 10x difference.
34const uint32_t kRatioBuckets = 100;
35
36void HistogramSizeKB(pp::UMAPrivate& uma,
37                     const std::string& name, int32_t kb) {
38  if (kb < 0) return;
39  uma.HistogramCustomCounts(name,
40                            kb,
41                            kSizeKBMin, kSizeKBMax,
42                            kSizeKBBuckets);
43}
44
45void HistogramRatio(pp::UMAPrivate& uma,
46                    const std::string& name, int64_t a, int64_t b) {
47  if (a < 0 || b <= 0) return;
48  uma.HistogramCustomCounts(name,
49                            100 * a / b,
50                            kRatioMin, kRatioMax,
51                            kRatioBuckets);
52}
53
54std::string GetArchitectureAttributes(Plugin* plugin) {
55  pp::Var attrs_var(pp::PASS_REF,
56                    plugin->nacl_interface()->GetCpuFeatureAttrs());
57  return attrs_var.AsString();
58}
59
60void DidCacheHit(void* user_data, PP_FileHandle nexe_file_handle) {
61  PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data);
62  coordinator->BitcodeStreamCacheHit(nexe_file_handle);
63}
64
65void DidCacheMiss(void* user_data, int64_t expected_pexe_size,
66                  PP_FileHandle temp_nexe_file) {
67  PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data);
68  coordinator->BitcodeStreamCacheMiss(expected_pexe_size,
69                                      temp_nexe_file);
70}
71
72void DidStreamData(void* user_data, const void* stream_data, int32_t length) {
73  PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data);
74  coordinator->BitcodeStreamGotData(stream_data, length);
75}
76
77void DidFinishStream(void* user_data, int32_t pp_error) {
78  PnaclCoordinator* coordinator = static_cast<PnaclCoordinator*>(user_data);
79  coordinator->BitcodeStreamDidFinish(pp_error);
80}
81
82PPP_PexeStreamHandler kPexeStreamHandler = {
83  &DidCacheHit,
84  &DidCacheMiss,
85  &DidStreamData,
86  &DidFinishStream
87};
88
89}  // namespace
90
91PnaclCoordinator* PnaclCoordinator::BitcodeToNative(
92    Plugin* plugin,
93    const std::string& pexe_url,
94    const PP_PNaClOptions& pnacl_options,
95    const pp::CompletionCallback& translate_notify_callback) {
96  PLUGIN_PRINTF(("PnaclCoordinator::BitcodeToNative (plugin=%p, pexe=%s)\n",
97                 static_cast<void*>(plugin), pexe_url.c_str()));
98  PnaclCoordinator* coordinator =
99      new PnaclCoordinator(plugin, pexe_url,
100                           pnacl_options,
101                           translate_notify_callback);
102
103  GetNaClInterface()->SetPNaClStartTime(plugin->pp_instance());
104  int cpus = plugin->nacl_interface()->GetNumberOfProcessors();
105  coordinator->split_module_count_ = std::min(4, std::max(1, cpus));
106
107  // First start a network request for the pexe, to tickle the component
108  // updater's On-Demand resource throttler, and to get Last-Modified/ETag
109  // cache information. We can cancel the request later if there's
110  // a bitcode->nexe cache hit.
111  coordinator->OpenBitcodeStream();
112  return coordinator;
113}
114
115PnaclCoordinator::PnaclCoordinator(
116    Plugin* plugin,
117    const std::string& pexe_url,
118    const PP_PNaClOptions& pnacl_options,
119    const pp::CompletionCallback& translate_notify_callback)
120  : translate_finish_error_(PP_OK),
121    plugin_(plugin),
122    translate_notify_callback_(translate_notify_callback),
123    translation_finished_reported_(false),
124    pexe_url_(pexe_url),
125    pnacl_options_(pnacl_options),
126    architecture_attributes_(GetArchitectureAttributes(plugin)),
127    split_module_count_(1),
128    error_already_reported_(false),
129    pexe_size_(0),
130    pexe_bytes_compiled_(0),
131    expected_pexe_size_(-1) {
132  callback_factory_.Initialize(this);
133}
134
135PnaclCoordinator::~PnaclCoordinator() {
136  PLUGIN_PRINTF(("PnaclCoordinator::~PnaclCoordinator (this=%p, "
137                 "translate_thread=%p\n",
138                 static_cast<void*>(this), translate_thread_.get()));
139  // Stopping the translate thread will cause the translate thread to try to
140  // run translation_complete_callback_ on the main thread.  This destructor is
141  // running from the main thread, and by the time it exits, callback_factory_
142  // will have been destroyed.  This will result in the cancellation of
143  // translation_complete_callback_, so no notification will be delivered.
144  if (translate_thread_.get() != NULL)
145    translate_thread_->AbortSubprocesses();
146  if (!translation_finished_reported_) {
147    plugin_->nacl_interface()->ReportTranslationFinished(
148        plugin_->pp_instance(),
149        PP_FALSE, 0, 0, 0);
150  }
151  // Force deleting the translate_thread now. It must be deleted
152  // before any scoped_* fields hanging off of PnaclCoordinator
153  // since the thread may be accessing those fields.
154  // It will also be accessing obj_files_.
155  translate_thread_.reset(NULL);
156  for (size_t i = 0; i < obj_files_.size(); i++)
157    delete obj_files_[i];
158}
159
160PP_FileHandle PnaclCoordinator::TakeTranslatedFileHandle() {
161  DCHECK(temp_nexe_file_ != NULL);
162  return temp_nexe_file_->TakeFileHandle();
163}
164
165void PnaclCoordinator::ReportNonPpapiError(PP_NaClError err_code,
166                                           const std::string& message) {
167  ErrorInfo error_info;
168  error_info.SetReport(err_code, message);
169  plugin_->ReportLoadError(error_info);
170  ExitWithError();
171}
172
173void PnaclCoordinator::ReportPpapiError(PP_NaClError err_code,
174                                        int32_t pp_error,
175                                        const std::string& message) {
176  std::stringstream ss;
177  ss << "PnaclCoordinator: " << message << " (pp_error=" << pp_error << ").";
178  ErrorInfo error_info;
179  error_info.SetReport(err_code, ss.str());
180  plugin_->ReportLoadError(error_info);
181  ExitWithError();
182}
183
184void PnaclCoordinator::ExitWithError() {
185  PLUGIN_PRINTF(("PnaclCoordinator::ExitWithError\n"));
186  // Free all the intermediate callbacks we ever created.
187  // Note: this doesn't *cancel* the callbacks from the factories attached
188  // to the various helper classes (e.g., pnacl_resources). Thus, those
189  // callbacks may still run asynchronously.  We let those run but ignore
190  // any other errors they may generate so that they do not end up running
191  // translate_notify_callback_, which has already been freed.
192  callback_factory_.CancelAll();
193  if (!error_already_reported_) {
194    error_already_reported_ = true;
195    translation_finished_reported_ = true;
196    plugin_->nacl_interface()->ReportTranslationFinished(
197        plugin_->pp_instance(),
198        PP_FALSE, 0, 0, 0);
199    translate_notify_callback_.Run(PP_ERROR_FAILED);
200  }
201}
202
203// Signal that Pnacl translation completed normally.
204void PnaclCoordinator::TranslateFinished(int32_t pp_error) {
205  PLUGIN_PRINTF(("PnaclCoordinator::TranslateFinished (pp_error=%"
206                 NACL_PRId32 ")\n", pp_error));
207  // Bail out if there was an earlier error (e.g., pexe load failure),
208  // or if there is an error from the translation thread.
209  if (translate_finish_error_ != PP_OK || pp_error != PP_OK) {
210    plugin_->ReportLoadError(error_info_);
211    ExitWithError();
212    return;
213  }
214
215  // Send out one last progress event, to finish up the progress events
216  // that were delayed (see the delay inserted in BitcodeGotCompiled).
217  if (expected_pexe_size_ != -1) {
218    pexe_bytes_compiled_ = expected_pexe_size_;
219    GetNaClInterface()->DispatchEvent(plugin_->pp_instance(),
220                                      PP_NACL_EVENT_PROGRESS,
221                                      pexe_url_.c_str(),
222                                      PP_TRUE,
223                                      pexe_bytes_compiled_,
224                                      expected_pexe_size_);
225  }
226  struct nacl_abi_stat stbuf;
227  struct NaClDesc* desc = temp_nexe_file_->read_wrapper()->desc();
228  if (0 == (*((struct NaClDescVtbl const *)desc->base.vtbl)->Fstat)(desc,
229                                                                    &stbuf)) {
230    size_t nexe_size = stbuf.nacl_abi_st_size;
231    HistogramSizeKB(plugin_->uma_interface(),
232                    "NaCl.Perf.Size.PNaClTranslatedNexe",
233                    static_cast<int64_t>(nexe_size / 1024));
234    HistogramRatio(plugin_->uma_interface(),
235                   "NaCl.Perf.Size.PexeNexeSizePct", pexe_size_, nexe_size);
236  }
237  // The nexe is written to the temp_nexe_file_.  We must Reset() the file
238  // pointer to be able to read it again from the beginning.
239  temp_nexe_file_->Reset();
240
241  // Report to the browser that translation finished. The browser will take
242  // care of storing the nexe in the cache.
243  translation_finished_reported_ = true;
244  plugin_->nacl_interface()->ReportTranslationFinished(
245      plugin_->pp_instance(), PP_TRUE, pnacl_options_.opt_level,
246      pexe_size_, translate_thread_->GetCompileTime());
247
248  NexeReadDidOpen(PP_OK);
249}
250
251void PnaclCoordinator::NexeReadDidOpen(int32_t pp_error) {
252  PLUGIN_PRINTF(("PnaclCoordinator::NexeReadDidOpen (pp_error=%"
253                 NACL_PRId32 ")\n", pp_error));
254  if (pp_error != PP_OK) {
255    if (pp_error == PP_ERROR_FILENOTFOUND) {
256      ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_NOTFOUND,
257                       pp_error,
258                       "Failed to open translated nexe (not found).");
259      return;
260    }
261    if (pp_error == PP_ERROR_NOACCESS) {
262      ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_NOACCESS,
263                       pp_error,
264                       "Failed to open translated nexe (no access).");
265      return;
266    }
267    ReportPpapiError(PP_NACL_ERROR_PNACL_CACHE_FETCH_OTHER,
268                     pp_error,
269                     "Failed to open translated nexe.");
270    return;
271  }
272
273  translate_notify_callback_.Run(PP_OK);
274}
275
276void PnaclCoordinator::OpenBitcodeStream() {
277  // Even though we haven't started downloading, create the translation
278  // thread object immediately. This ensures that any pieces of the file
279  // that get downloaded before the compilation thread is accepting
280  // SRPCs won't get dropped.
281  translate_thread_.reset(new PnaclTranslateThread());
282  if (translate_thread_ == NULL) {
283    ReportNonPpapiError(
284        PP_NACL_ERROR_PNACL_THREAD_CREATE,
285        "PnaclCoordinator: could not allocate translation thread.");
286    return;
287  }
288
289  GetNaClInterface()->StreamPexe(plugin_->pp_instance(),
290                                 pexe_url_.c_str(),
291                                 pnacl_options_.opt_level,
292                                 &kPexeStreamHandler,
293                                 this);
294}
295
296void PnaclCoordinator::BitcodeStreamCacheHit(PP_FileHandle handle) {
297  if (handle == PP_kInvalidFileHandle) {
298    ReportNonPpapiError(
299        PP_NACL_ERROR_PNACL_CREATE_TEMP,
300        std::string(
301            "PnaclCoordinator: Got bad temp file handle from GetNexeFd"));
302    BitcodeStreamDidFinish(PP_ERROR_FAILED);
303    return;
304  }
305  temp_nexe_file_.reset(new TempFile(plugin_, handle));
306  // Open it for reading as the cached nexe file.
307  NexeReadDidOpen(temp_nexe_file_->Open(false));
308}
309
310void PnaclCoordinator::BitcodeStreamCacheMiss(int64_t expected_pexe_size,
311                                              PP_FileHandle nexe_handle) {
312  // IMPORTANT: Make sure that PnaclResources::StartLoad() is only
313  // called after you receive a response to a request for a .pexe file.
314  //
315  // The component updater's resource throttles + OnDemand update/install
316  // should block the URL request until the compiler is present. Now we
317  // can load the resources (e.g. llc and ld nexes).
318  resources_.reset(new PnaclResources(plugin_));
319  CHECK(resources_ != NULL);
320
321  // The first step of loading resources: read the resource info file.
322  if (!resources_->ReadResourceInfo()) {
323    ExitWithError();
324    return;
325  }
326
327  // Second step of loading resources: call StartLoad to load pnacl-llc
328  // and pnacl-ld, based on the filenames found in the resource info file.
329  if (!resources_->StartLoad()) {
330    ReportNonPpapiError(
331        PP_NACL_ERROR_PNACL_RESOURCE_FETCH,
332        std::string("The Portable Native Client (pnacl) component is not "
333                     "installed. Please consult chrome://components for more "
334                      "information."));
335    return;
336  }
337
338  expected_pexe_size_ = expected_pexe_size;
339
340  for (int i = 0; i < split_module_count_; i++) {
341    PP_FileHandle obj_handle =
342        plugin_->nacl_interface()->CreateTemporaryFile(plugin_->pp_instance());
343    nacl::scoped_ptr<TempFile> temp_file(new TempFile(plugin_, obj_handle));
344    int32_t pp_error = temp_file->Open(true);
345    if (pp_error != PP_OK) {
346      ReportPpapiError(PP_NACL_ERROR_PNACL_CREATE_TEMP,
347                       pp_error,
348                       "Failed to open scratch object file.");
349      return;
350    } else {
351      obj_files_.push_back(temp_file.release());
352    }
353  }
354  invalid_desc_wrapper_.reset(plugin_->wrapper_factory()->MakeInvalid());
355
356  temp_nexe_file_.reset(new TempFile(plugin_, nexe_handle));
357  // Open the nexe file for connecting ld and sel_ldr.
358  // Start translation when done with this last step of setup!
359  RunTranslate(temp_nexe_file_->Open(true));
360}
361
362void PnaclCoordinator::BitcodeStreamGotData(const void* data, int32_t length) {
363  DCHECK(translate_thread_.get());
364
365  translate_thread_->PutBytes(data, length);
366  if (data && length > 0)
367    pexe_size_ += length;
368}
369
370void PnaclCoordinator::BitcodeStreamDidFinish(int32_t pp_error) {
371  PLUGIN_PRINTF(("PnaclCoordinator::BitcodeStreamDidFinish (pp_error=%"
372                 NACL_PRId32 ")\n", pp_error));
373  if (pp_error != PP_OK) {
374    // Defer reporting the error and cleanup until after the translation
375    // thread returns, because it may be accessing the coordinator's
376    // objects or writing to the files.
377    translate_finish_error_ = pp_error;
378    if (pp_error == PP_ERROR_ABORTED) {
379      error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_ABORTED,
380                            "PnaclCoordinator: pexe load failed (aborted).");
381    }
382    if (pp_error == PP_ERROR_NOACCESS) {
383      error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_NOACCESS,
384                            "PnaclCoordinator: pexe load failed (no access).");
385    } else {
386      std::stringstream ss;
387      ss << "PnaclCoordinator: pexe load failed (pp_error=" << pp_error << ").";
388      error_info_.SetReport(PP_NACL_ERROR_PNACL_PEXE_FETCH_OTHER, ss.str());
389    }
390
391    if (translate_thread_->started())
392      translate_thread_->AbortSubprocesses();
393    else
394      TranslateFinished(pp_error);
395  } else {
396    // Compare download completion pct (100% now), to compile completion pct.
397    HistogramRatio(plugin_->uma_interface(),
398                   "NaCl.Perf.PNaClLoadTime.PctCompiledWhenFullyDownloaded",
399                   pexe_bytes_compiled_, pexe_size_);
400    translate_thread_->EndStream();
401  }
402}
403
404void PnaclCoordinator::BitcodeGotCompiled(int32_t pp_error,
405                                          int64_t bytes_compiled) {
406  DCHECK(pp_error == PP_OK);
407  pexe_bytes_compiled_ += bytes_compiled;
408  // Hold off reporting the last few bytes of progress, since we don't know
409  // when they are actually completely compiled.  "bytes_compiled" only means
410  // that bytes were sent to the compiler.
411  if (expected_pexe_size_ != -1) {
412    if (!ShouldDelayProgressEvent()) {
413      GetNaClInterface()->DispatchEvent(plugin_->pp_instance(),
414                                        PP_NACL_EVENT_PROGRESS,
415                                        pexe_url_.c_str(),
416                                        PP_TRUE,
417                                        pexe_bytes_compiled_,
418                                        expected_pexe_size_);
419    }
420  } else {
421    GetNaClInterface()->DispatchEvent(plugin_->pp_instance(),
422                                      PP_NACL_EVENT_PROGRESS,
423                                      pexe_url_.c_str(),
424                                      PP_FALSE,
425                                      pexe_bytes_compiled_,
426                                      expected_pexe_size_);
427  }
428}
429
430pp::CompletionCallback PnaclCoordinator::GetCompileProgressCallback(
431    int64_t bytes_compiled) {
432  return callback_factory_.NewCallback(&PnaclCoordinator::BitcodeGotCompiled,
433                                       bytes_compiled);
434}
435
436void PnaclCoordinator::GetCurrentProgress(int64_t* bytes_loaded,
437                                          int64_t* bytes_total) {
438  *bytes_loaded = pexe_bytes_compiled_;
439  *bytes_total = expected_pexe_size_;
440}
441
442void PnaclCoordinator::RunTranslate(int32_t pp_error) {
443  PLUGIN_PRINTF(("PnaclCoordinator::RunTranslate (pp_error=%"
444                 NACL_PRId32 ")\n", pp_error));
445  // Invoke llc followed by ld off the main thread.  This allows use of
446  // blocking RPCs that would otherwise block the JavaScript main thread.
447  pp::CompletionCallback report_translate_finished =
448      callback_factory_.NewCallback(&PnaclCoordinator::TranslateFinished);
449
450  CHECK(translate_thread_ != NULL);
451  translate_thread_->RunTranslate(report_translate_finished,
452                                  &obj_files_,
453                                  temp_nexe_file_.get(),
454                                  invalid_desc_wrapper_.get(),
455                                  &error_info_,
456                                  resources_.get(),
457                                  &pnacl_options_,
458                                  architecture_attributes_,
459                                  this,
460                                  plugin_);
461}
462
463}  // namespace plugin
464