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