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