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