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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "components/nacl/renderer/plugin/pnacl_translate_thread.h"
11
12 #include <stddef.h>
13
14 #include <iterator>
15 #include <memory>
16 #include <sstream>
17
18 #include "base/check.h"
19 #include "base/time/time.h"
20 #include "components/nacl/renderer/plugin/plugin.h"
21 #include "components/nacl/renderer/plugin/plugin_error.h"
22 #include "ppapi/c/ppb_file_io.h"
23 #include "ppapi/cpp/var.h"
24 #include "ppapi/proxy/ppapi_messages.h"
25
26 namespace plugin {
27 namespace {
28
29 template <typename Val>
MakeCommandLineArg(const char * key,const Val val)30 std::string MakeCommandLineArg(const char* key, const Val val) {
31 std::stringstream ss;
32 ss << key << val;
33 return ss.str();
34 }
35
GetLlcCommandLine(std::vector<std::string> * args,size_t obj_files_size,int32_t opt_level,bool is_debug,const std::string & architecture_attributes)36 void GetLlcCommandLine(std::vector<std::string>* args,
37 size_t obj_files_size,
38 int32_t opt_level,
39 bool is_debug,
40 const std::string& architecture_attributes) {
41 // TODO(dschuff): This CL override is ugly. Change llc to default to
42 // using the number of modules specified in the first param, and
43 // ignore multiple uses of -split-module
44 args->push_back(MakeCommandLineArg("-split-module=", obj_files_size));
45 args->push_back(MakeCommandLineArg("-O", opt_level));
46 if (is_debug)
47 args->push_back("-bitcode-format=llvm");
48 if (!architecture_attributes.empty())
49 args->push_back("-mattr=" + architecture_attributes);
50 }
51
GetSubzeroCommandLine(std::vector<std::string> * args,int32_t opt_level,bool is_debug,const std::string & architecture_attributes)52 void GetSubzeroCommandLine(std::vector<std::string>* args,
53 int32_t opt_level,
54 bool is_debug,
55 const std::string& architecture_attributes) {
56 args->push_back(MakeCommandLineArg("-O", opt_level));
57 DCHECK(!is_debug);
58 // TODO(stichnot): enable this once the mattr flag formatting is
59 // compatible: https://code.google.com/p/nativeclient/issues/detail?id=4132
60 // if (!architecture_attributes.empty())
61 // args->push_back("-mattr=" + architecture_attributes);
62 }
63
64 } // namespace
65
PnaclTranslateThread()66 PnaclTranslateThread::PnaclTranslateThread()
67 : compiler_subprocess_(nullptr),
68 ld_subprocess_(nullptr),
69 compiler_subprocess_active_(false),
70 ld_subprocess_active_(false),
71 buffer_cond_(&cond_mu_),
72 done_(false),
73 compile_time_(0),
74 obj_files_(nullptr),
75 num_threads_(0),
76 nexe_file_(nullptr),
77 coordinator_error_info_(nullptr),
78 coordinator_(nullptr) {}
79
SetupState(const pp::CompletionCallback & finish_callback,NaClSubprocess * compiler_subprocess,NaClSubprocess * ld_subprocess,std::vector<base::File> * obj_files,int num_threads,base::File * nexe_file,ErrorInfo * error_info,PP_PNaClOptions * pnacl_options,const std::string & architecture_attributes,PnaclCoordinator * coordinator)80 void PnaclTranslateThread::SetupState(
81 const pp::CompletionCallback& finish_callback,
82 NaClSubprocess* compiler_subprocess,
83 NaClSubprocess* ld_subprocess,
84 std::vector<base::File>* obj_files,
85 int num_threads,
86 base::File* nexe_file,
87 ErrorInfo* error_info,
88 PP_PNaClOptions* pnacl_options,
89 const std::string& architecture_attributes,
90 PnaclCoordinator* coordinator) {
91 compiler_subprocess_ = compiler_subprocess;
92 ld_subprocess_ = ld_subprocess;
93 obj_files_ = obj_files;
94 num_threads_ = num_threads;
95 nexe_file_ = nexe_file;
96 coordinator_error_info_ = error_info;
97 pnacl_options_ = pnacl_options;
98 architecture_attributes_ = architecture_attributes;
99 coordinator_ = coordinator;
100
101 report_translate_finished_ = finish_callback;
102 }
103
RunCompile(const pp::CompletionCallback & compile_finished_callback)104 void PnaclTranslateThread::RunCompile(
105 const pp::CompletionCallback& compile_finished_callback) {
106 DCHECK(started());
107 DCHECK(compiler_subprocess_->service_runtime());
108 compiler_subprocess_active_ = true;
109
110 // Take ownership of this IPC channel to make sure that it does not get
111 // freed on the child thread when the child thread calls Shutdown().
112 compiler_channel_ =
113 compiler_subprocess_->service_runtime()->TakeTranslatorChannel();
114 // compiler_channel_ is a IPC::SyncChannel, which is not thread-safe and
115 // cannot be used directly by the child thread, so create a
116 // SyncMessageFilter which can be used by the child thread.
117 compiler_channel_filter_ = compiler_channel_->CreateSyncMessageFilter();
118
119 compile_finished_callback_ = compile_finished_callback;
120 translate_thread_ = std::make_unique<CompileThread>(this);
121 translate_thread_->Start();
122 }
123
RunLink()124 void PnaclTranslateThread::RunLink() {
125 DCHECK(started());
126 DCHECK(ld_subprocess_->service_runtime());
127 ld_subprocess_active_ = true;
128
129 // Take ownership of this IPC channel to make sure that it does not get
130 // freed on the child thread when the child thread calls Shutdown().
131 ld_channel_ = ld_subprocess_->service_runtime()->TakeTranslatorChannel();
132 // ld_channel_ is a IPC::SyncChannel, which is not thread-safe and cannot be
133 // used directly by the child thread, so create a SyncMessageFilter which
134 // can be used by the child thread.
135 ld_channel_filter_ = ld_channel_->CreateSyncMessageFilter();
136
137 // Tear down the previous thread.
138 translate_thread_->Join();
139 translate_thread_ = std::make_unique<LinkThread>(this);
140 translate_thread_->Start();
141 }
142
143 // Called from main thread to send bytes to the translator.
PutBytes(const void * bytes,int32_t count)144 void PnaclTranslateThread::PutBytes(const void* bytes, int32_t count) {
145 CHECK(bytes);
146 base::AutoLock lock(cond_mu_);
147 data_buffers_.push_back(std::string());
148 data_buffers_.back().insert(data_buffers_.back().end(),
149 static_cast<const char*>(bytes),
150 static_cast<const char*>(bytes) + count);
151 buffer_cond_.Signal();
152 }
153
EndStream()154 void PnaclTranslateThread::EndStream() {
155 base::AutoLock lock(cond_mu_);
156 done_ = true;
157 buffer_cond_.Signal();
158 }
159
GetHandleForSubprocess(base::File * file,int32_t open_flags)160 ppapi::proxy::SerializedHandle PnaclTranslateThread::GetHandleForSubprocess(
161 base::File* file,
162 int32_t open_flags) {
163 DCHECK(file->IsValid());
164 IPC::PlatformFileForTransit file_for_transit =
165 IPC::GetPlatformFileForTransit(file->GetPlatformFile(), false);
166
167 // Using 0 disables any use of quota enforcement for this file handle.
168 PP_Resource file_io = 0;
169
170 ppapi::proxy::SerializedHandle handle;
171 handle.set_file_handle(file_for_transit, open_flags, file_io);
172 return handle;
173 }
174
Run()175 void PnaclTranslateThread::CompileThread::Run() {
176 pnacl_translate_thread_->DoCompile();
177 }
178
DoCompile()179 void PnaclTranslateThread::DoCompile() {
180 {
181 base::AutoLock lock(subprocess_mu_);
182 // If the main thread asked us to exit in between starting the thread
183 // and now, just leave now.
184 if (!compiler_subprocess_active_)
185 return;
186 }
187
188 std::vector<ppapi::proxy::SerializedHandle> compiler_output_files;
189 for (base::File& obj_file : *obj_files_) {
190 compiler_output_files.push_back(
191 GetHandleForSubprocess(&obj_file, PP_FILEOPENFLAG_WRITE));
192 }
193
194 pp::Core* core = pp::Module::Get()->core();
195 base::TimeTicks do_compile_start_time = base::TimeTicks::Now();
196
197 std::vector<std::string> args;
198 if (pnacl_options_->use_subzero) {
199 GetSubzeroCommandLine(&args, pnacl_options_->opt_level,
200 PP_ToBool(pnacl_options_->is_debug),
201 architecture_attributes_);
202 } else {
203 GetLlcCommandLine(&args, obj_files_->size(),
204 pnacl_options_->opt_level,
205 PP_ToBool(pnacl_options_->is_debug),
206 architecture_attributes_);
207 }
208
209 bool success = false;
210 std::string error_str;
211 if (!compiler_channel_filter_->Send(
212 new PpapiMsg_PnaclTranslatorCompileInit(
213 num_threads_, compiler_output_files, args, &success, &error_str))) {
214 TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL,
215 "Compile stream init failed: "
216 "reply not received from PNaCl translator "
217 "(it probably crashed)");
218 return;
219 }
220 if (!success) {
221 TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL,
222 std::string("Stream init failed: ") + error_str);
223 return;
224 }
225
226 // llc process is started.
227 while(!done_ || data_buffers_.size() > 0) {
228 cond_mu_.Acquire();
229 while(!done_ && data_buffers_.size() == 0) {
230 buffer_cond_.Wait();
231 }
232 if (data_buffers_.size() > 0) {
233 std::string data;
234 data.swap(data_buffers_.front());
235 data_buffers_.pop_front();
236 cond_mu_.Release();
237
238 if (!compiler_channel_filter_->Send(
239 new PpapiMsg_PnaclTranslatorCompileChunk(data, &success))) {
240 TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL,
241 "Compile stream chunk failed: "
242 "reply not received from PNaCl translator "
243 "(it probably crashed)");
244 return;
245 }
246 if (!success) {
247 // If the error was reported by the translator, then we fall through
248 // and call PpapiMsg_PnaclTranslatorCompileEnd, which returns a string
249 // describing the error, which we can then send to the Javascript
250 // console.
251 break;
252 }
253 core->CallOnMainThread(
254 0,
255 coordinator_->GetCompileProgressCallback(data.size()),
256 PP_OK);
257 } else {
258 cond_mu_.Release();
259 }
260 }
261 // Finish llc.
262 if (!compiler_channel_filter_->Send(
263 new PpapiMsg_PnaclTranslatorCompileEnd(&success, &error_str))) {
264 TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL,
265 "Compile stream end failed: "
266 "reply not received from PNaCl translator "
267 "(it probably crashed)");
268 return;
269 }
270 if (!success) {
271 TranslateFailed(PP_NACL_ERROR_PNACL_LLC_INTERNAL, error_str);
272 return;
273 }
274 compile_time_ =
275 (base::TimeTicks::Now() - do_compile_start_time).InMicroseconds();
276 nacl::PPBNaClPrivate::LogTranslateTime("NaCl.Perf.PNaClLoadTime.CompileTime",
277 compile_time_);
278 nacl::PPBNaClPrivate::LogTranslateTime(
279 pnacl_options_->use_subzero
280 ? "NaCl.Perf.PNaClLoadTime.CompileTime.Subzero"
281 : "NaCl.Perf.PNaClLoadTime.CompileTime.LLC",
282 compile_time_);
283
284 // Shut down the compiler subprocess.
285 {
286 base::AutoLock lock(subprocess_mu_);
287 compiler_subprocess_active_ = false;
288 }
289
290 core->CallOnMainThread(0, compile_finished_callback_, PP_OK);
291 }
292
Run()293 void PnaclTranslateThread::LinkThread::Run() {
294 pnacl_translate_thread_->DoLink();
295 }
296
DoLink()297 void PnaclTranslateThread::DoLink() {
298 {
299 base::AutoLock lock(subprocess_mu_);
300 // If the main thread asked us to exit in between starting the thread
301 // and now, just leave now.
302 if (!ld_subprocess_active_)
303 return;
304 }
305
306 // Reset object files for reading first. We do this before duplicating
307 // handles/FDs to prevent any handle/FD leaks in case any of the Seek()
308 // calls fail.
309 for (base::File& obj_file : *obj_files_) {
310 if (obj_file.Seek(base::File::FROM_BEGIN, 0) != 0) {
311 TranslateFailed(PP_NACL_ERROR_PNACL_LD_SETUP,
312 "Link process could not reset object file");
313 return;
314 }
315 }
316
317 ppapi::proxy::SerializedHandle nexe_file =
318 GetHandleForSubprocess(nexe_file_, PP_FILEOPENFLAG_WRITE);
319 std::vector<ppapi::proxy::SerializedHandle> ld_input_files;
320 for (base::File& obj_file : *obj_files_) {
321 ld_input_files.push_back(
322 GetHandleForSubprocess(&obj_file, PP_FILEOPENFLAG_READ));
323 }
324
325 base::TimeTicks link_start_time = base::TimeTicks::Now();
326 bool success = false;
327 bool sent = ld_channel_filter_->Send(
328 new PpapiMsg_PnaclTranslatorLink(ld_input_files, nexe_file, &success));
329 if (!sent) {
330 TranslateFailed(PP_NACL_ERROR_PNACL_LD_INTERNAL,
331 "link failed: reply not received from linker.");
332 return;
333 }
334 if (!success) {
335 TranslateFailed(PP_NACL_ERROR_PNACL_LD_INTERNAL,
336 "link failed: linker returned failure status.");
337 return;
338 }
339
340 nacl::PPBNaClPrivate::LogTranslateTime(
341 "NaCl.Perf.PNaClLoadTime.LinkTime",
342 (base::TimeTicks::Now() - link_start_time).InMicroseconds());
343
344 // Shut down the ld subprocess.
345 {
346 base::AutoLock lock(subprocess_mu_);
347 ld_subprocess_active_ = false;
348 }
349
350 pp::Core* core = pp::Module::Get()->core();
351 core->CallOnMainThread(0, report_translate_finished_, PP_OK);
352 }
353
TranslateFailed(PP_NaClError err_code,const std::string & error_string)354 void PnaclTranslateThread::TranslateFailed(
355 PP_NaClError err_code,
356 const std::string& error_string) {
357 pp::Core* core = pp::Module::Get()->core();
358 if (coordinator_error_info_->message().empty()) {
359 // Only use our message if one hasn't already been set by the coordinator
360 // (e.g. pexe load failed).
361 coordinator_error_info_->SetReport(err_code,
362 std::string("PnaclCoordinator: ") +
363 error_string);
364 }
365 core->CallOnMainThread(0, report_translate_finished_, PP_ERROR_FAILED);
366 }
367
AbortSubprocesses()368 void PnaclTranslateThread::AbortSubprocesses() {
369 {
370 base::AutoLock lock(subprocess_mu_);
371 if (compiler_subprocess_ && compiler_subprocess_active_) {
372 // We only run the service_runtime's Shutdown and do not run the
373 // NaClSubprocess Shutdown, which would otherwise nullify some
374 // pointers that could still be in use (srpc_client, etc.).
375 compiler_subprocess_->service_runtime()->Shutdown();
376 compiler_subprocess_active_ = false;
377 }
378 if (ld_subprocess_ && ld_subprocess_active_) {
379 ld_subprocess_->service_runtime()->Shutdown();
380 ld_subprocess_active_ = false;
381 }
382 }
383 base::AutoLock lock(cond_mu_);
384 done_ = true;
385 // Free all buffered bitcode chunks
386 data_buffers_.clear();
387 buffer_cond_.Signal();
388 }
389
~PnaclTranslateThread()390 PnaclTranslateThread::~PnaclTranslateThread() {
391 AbortSubprocesses();
392 if (translate_thread_)
393 translate_thread_->Join();
394 }
395
396 } // namespace plugin
397