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