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 |