Add mode to EndCatch
[hiphop-php.git] / hphp / runtime / vm / extern-compiler.cpp
blob2215367bd3e6e0c9367d13dc2925f81886009e68
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/extern-compiler.h"
19 #include <cinttypes>
20 #include <condition_variable>
21 #include <mutex>
22 #include <signal.h>
23 #include <sstream>
24 #include <stdio.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
28 #include <folly/DynamicConverter.h>
29 #include <folly/json.h>
30 #include <folly/FileUtil.h>
32 #include "hphp/runtime/base/ini-setting.h"
33 #include "hphp/runtime/base/zend-strtod.h"
34 #include "hphp/runtime/server/source-root-info.h"
35 #include "hphp/runtime/vm/native.h"
36 #include "hphp/runtime/vm/repo.h"
37 #include "hphp/runtime/vm/unit-emitter.h"
38 #include "hphp/util/atomic-vector.h"
39 #include "hphp/util/embedded-data.h"
40 #include "hphp/util/gzip.h"
41 #include "hphp/util/light-process.h"
42 #include "hphp/util/logger.h"
43 #include "hphp/util/match.h"
44 #include "hphp/util/sha1.h"
45 #include "hphp/util/struct-log.h"
46 #include "hphp/util/timer.h"
48 #include <iostream>
50 namespace HPHP {
52 TRACE_SET_MOD(extern_compiler);
54 namespace {
56 bool createHackc(const std::string& path, const std::string& binary) {
57 if (access(path.c_str(), R_OK|X_OK) == 0) {
58 auto const fd = open(path.c_str(), O_RDONLY);
59 if (fd != -1) {
60 SCOPE_EXIT { close(fd); };
61 std::string contents;
62 if (folly::readFile(fd, contents) && contents == binary) return true;
65 try {
66 folly::writeFileAtomic(path, binary, 0755);
67 } catch (std::system_error& ex) {
68 return false;
70 return true;
73 THREAD_LOCAL(std::string, tl_extractPath);
75 std::mutex s_extractLock;
76 std::string s_extractPath;
78 folly::Optional<std::string> hackcExtractPath() {
79 if (!RuntimeOption::EvalHackCompilerUseEmbedded) return folly::none;
81 auto check = [] (const std::string& s) {
82 return !s.empty() && access(s.data(), X_OK) == 0;
84 if (!tl_extractPath.isNull() && check(*tl_extractPath)) {
85 return *tl_extractPath;
88 std::unique_lock<std::mutex> lock{s_extractLock};
89 if (check(s_extractPath)) {
90 *tl_extractPath.getCheck() = s_extractPath;
91 return *tl_extractPath;
94 auto set = [&] (const std::string& s) {
95 s_extractPath = s;
96 *tl_extractPath.getCheck() = s;
97 return s;
100 auto const trust = RuntimeOption::EvalHackCompilerTrustExtract;
101 auto const location = RuntimeOption::EvalHackCompilerExtractPath;
102 // As an optimization we can just choose to trust the extracted version
103 // without reading it.
104 if (trust && check(location)) return set(location);
106 embedded_data desc;
107 if (!get_embedded_data("hackc_binary", &desc)) {
108 Logger::Error("Embedded hackc binary is missing");
109 return folly::none;
111 auto const gz_binary = read_embedded_data(desc);
112 int len = safe_cast<int>(gz_binary.size());
114 auto const bin_str = gzdecode(gz_binary.data(), len);
115 SCOPE_EXIT { free(bin_str); };
116 if (!bin_str || !len) {
117 Logger::Error("Embedded hackc binary could not be decompressed");
118 return folly::none;
121 auto const binary = std::string(bin_str, len);
122 if (createHackc(location, binary)) return set(location);
124 int fd = -1;
125 SCOPE_EXIT { if (fd != -1) close(fd); };
127 auto fallback = RuntimeOption::EvalHackCompilerFallbackPath;
128 if ((fd = mkstemp(&fallback[0])) == -1) {
129 Logger::FError(
130 "Unable to create temp file for hackc binary: {}", folly::errnoStr(errno)
132 return folly::none;
135 if (folly::writeFull(fd, binary.data(), binary.size()) == -1) {
136 Logger::FError(
137 "Failed to write extern hackc binary: {}", folly::errnoStr(errno)
139 return folly::none;
142 if (chmod(fallback.data(), 0755) != 0) {
143 Logger::Error("Unable to mark hackc binary as writable");
144 return folly::none;
147 return set(fallback);
150 std::string hackcCommand() {
151 if (auto path = hackcExtractPath()) {
152 return *path + " " + RuntimeOption::EvalHackCompilerArgs;
154 return RuntimeOption::EvalHackCompilerCommand;
157 struct CompileException : Exception {
158 explicit CompileException(const std::string& what) : Exception(what) {}
159 template<class... A>
160 explicit CompileException(A&&... args)
161 : Exception(folly::sformat(std::forward<A>(args)...))
165 struct CompilerFatal : std::runtime_error {
166 explicit CompilerFatal(const std::string& str)
167 : std::runtime_error(str)
171 [[noreturn]] void throwErrno(const char* what) {
172 throw CompileException("{}: {}", what, folly::errnoStr(errno));
175 struct CompilerOptions {
176 bool verboseErrors;
177 uint64_t maxRetries;
178 uint64_t workers;
179 bool inheritConfig;
182 constexpr int kInvalidPid = -1;
184 struct ExternCompiler {
185 explicit ExternCompiler(const CompilerOptions& options)
186 : m_options(options)
188 ExternCompiler(ExternCompiler&&) = default;
189 ExternCompiler& operator=(ExternCompiler&&) = default;
190 void detach_from_process () {
191 // Called from forked processes. Resets inherited pid of compiler process to
192 // prevent it being closed in case child process exits
193 m_pid = kInvalidPid;
194 // Don't call the destructor of the thread object. The thread doesn't
195 // belong to this child process, so we just silently make sure we don't
196 // touch it.
197 m_logStderrThread.release();
199 ~ExternCompiler() { stop(); }
201 std::string extract_facts(
202 const std::string& filename,
203 folly::StringPiece code
205 if (!isRunning()) {
206 start();
208 std::string facts;
209 try {
210 writeExtractFacts(filename, code);
211 return readResult(nullptr /* structured log entry */);
213 catch (CompileException& ex) {
214 stop();
215 if (m_options.verboseErrors) {
216 Logger::FError("ExternCompiler Error (facts): {}", ex.what());
218 throw;
222 std::string ffp_parse_file(
223 const std::string& filename,
224 folly::StringPiece code
226 if (!isRunning()) {
227 start();
229 try {
230 writeParseFile(filename, code);
231 return readResult(nullptr /* structured log entry */);
233 catch (CompileException& ex) {
234 stop();
235 if (m_options.verboseErrors) {
236 Logger::FError("ExternCompiler Error (parse): {}", ex.what());
238 throw;
242 int64_t logTime(
243 StructuredLogEntry& log,
244 int64_t t,
245 const char* name,
246 bool first = false
248 if (!RuntimeOption::EvalLogExternCompilerPerf) return 0;
249 int64_t current = Timer::GetCurrentTimeMicros();
250 if (first) return current;
251 int64_t diff = current - t;
252 log.setInt(name, diff);
253 FTRACE(2, "{} took {} us\n", name, diff);
254 return current;
257 std::unique_ptr<UnitEmitter> compile(
258 const char* filename,
259 const SHA1& sha1,
260 folly::StringPiece code,
261 const Native::FuncTable& nativeFuncs,
262 bool forDebuggerEval,
263 bool wantsSymbolRefs,
264 const RepoOptions& options
266 if (!isRunning()) {
267 start();
270 std::string prog;
271 std::unique_ptr<Unit> u;
272 try {
273 m_compilations++;
274 StructuredLogEntry log;
275 log.setStr("filename", filename);
276 int64_t t = logTime(log, 0, nullptr, true);
277 writeProgram(filename, sha1, code, forDebuggerEval, options);
278 t = logTime(log, t, "send_source");
279 prog = readResult(&log);
280 t = logTime(log, t, "receive_hhas");
281 auto ue = assemble_string(prog.data(),
282 prog.length(),
283 filename,
284 sha1,
285 nativeFuncs,
286 false /* swallow errors */,
287 wantsSymbolRefs
289 logTime(log, t, "assemble_hhas");
290 if (RuntimeOption::EvalLogExternCompilerPerf) {
291 StructuredLog::log("hhvm_detailed_frontend_performance", log);
293 return ue;
294 } catch (CompileException& ex) {
295 stop();
296 if (m_options.verboseErrors) {
297 Logger::FError("ExternCompiler Error: {}", ex.what());
299 throw;
300 } catch (CompilerFatal& ex) {
301 // this catch is here so we don't fall into the std::runtime_error one
302 throw;
303 } catch (AssemblerFatal& ex) {
304 // this catch is here so we don't fall into the std::runtime_error one
305 throw;
306 } catch (FatalErrorException&) {
307 // we want these to propagate out of the compiler
308 throw;
309 } catch (AssemblerUnserializationError& ex) {
310 // This (probably) has nothing to do with the php/hhas, so don't do the
311 // verbose error handling we have in the AssemblerError case.
312 throw;
313 } catch (AssemblerError& ex) {
314 if (m_options.verboseErrors) {
315 auto const msg = folly::sformat(
316 "{}\n"
317 "========== PHP Source ==========\n"
318 "{}\n"
319 "========== ExternCompiler Result ==========\n"
320 "{}\n",
321 ex.what(),
322 code,
323 prog);
324 Logger::FError("ExternCompiler Generated a bad unit: {}", msg);
326 // Throw the extended message to ensure the fataling unit contains the
327 // additional context
328 throw AssemblerError(msg);
330 throw;
331 } catch (std::runtime_error& ex) {
332 if (m_options.verboseErrors) {
333 Logger::FError("ExternCompiler Runtime Error: {}", ex.what());
335 throw;
339 std::string getVersionString() {
340 if (!isRunning()) start();
341 return m_version;
344 private:
345 void start();
346 void stop();
347 bool isRunning() const { return m_pid != kInvalidPid; }
348 void stopLogStderrThread();
350 void writeMessage(folly::dynamic& header, folly::StringPiece body);
351 void writeConfigs();
352 void writeProgram(const char* filename, SHA1 sha1, folly::StringPiece code,
353 bool forDebuggerEval, const RepoOptions& options);
354 void writeExtractFacts(const std::string& filename, folly::StringPiece code);
355 void writeParseFile(const std::string& filename, folly::StringPiece code);
357 std::string readVersion() const;
358 std::string readResult(StructuredLogEntry* log) const;
360 pid_t m_pid{kInvalidPid};
361 FILE* m_in{nullptr};
362 FILE* m_out{nullptr};
363 std::string m_version;
365 FILE* m_err{nullptr};
366 std::unique_ptr<std::thread> m_logStderrThread;
368 unsigned m_compilations{0};
369 const CompilerOptions& m_options;
372 struct CompilerGuard;
374 struct CompilerPool {
375 explicit CompilerPool(CompilerOptions&& options)
376 : m_options(options)
377 , m_compilers(options.workers, nullptr)
380 std::pair<size_t, ExternCompiler*> getCompiler();
381 void releaseCompiler(size_t id, ExternCompiler* ptr);
382 void start();
383 void shutdown(bool detach_compilers);
384 CompilerResult compile(const char* code,
385 int len,
386 const char* filename,
387 const SHA1& sha1,
388 const Native::FuncTable& nativeFuncs,
389 bool forDebuggerEval,
390 bool wantsSymbolRefs,
391 bool& internal_error,
392 const RepoOptions& options);
393 FfpResult parse(std::string name, const char* code, int len);
394 ParseFactsResult extract_facts(const CompilerGuard& compiler,
395 const std::string& filename,
396 const char* code,
397 int len);
398 std::string getVersionString() { return m_version; }
399 std::pair<uint64_t, bool> getMaxRetriesAndVerbosity() const {
400 return std::make_pair(m_options.maxRetries, m_options.verboseErrors);
402 private:
403 CompilerOptions m_options;
404 std::atomic<size_t> m_freeCount{0};
405 std::mutex m_compilerLock;
406 std::condition_variable m_compilerCv;
407 AtomicVector<ExternCompiler*> m_compilers;
408 std::string m_version;
411 struct CompilerGuard final: public FactsParser {
412 explicit CompilerGuard(CompilerPool& pool)
413 : m_pool(pool) {
414 std::tie(m_index, m_ptr) = m_pool.getCompiler();
417 ~CompilerGuard() override {
418 m_pool.releaseCompiler(m_index, m_ptr);
421 CompilerGuard(CompilerGuard&&) = delete;
422 CompilerGuard& operator=(CompilerGuard&&) = delete;
424 ExternCompiler* operator->() const { return m_ptr; }
426 private:
427 size_t m_index;
428 ExternCompiler* m_ptr;
429 CompilerPool& m_pool;
432 std::pair<size_t, ExternCompiler*> CompilerPool::getCompiler() {
433 std::unique_lock<std::mutex> l(m_compilerLock);
435 m_compilerCv.wait(l, [&] {
436 return m_freeCount.load(std::memory_order_relaxed) != 0;
438 m_freeCount -= 1;
440 for (size_t id = 0; id < m_compilers.size(); ++id) {
441 auto ret = m_compilers.exchange(id, nullptr);
442 if (ret) return std::make_pair(id, ret);
445 not_reached();
448 void CompilerPool::releaseCompiler(size_t id, ExternCompiler* ptr) {
449 std::unique_lock<std::mutex> l(m_compilerLock);
451 m_compilers[id].store(ptr, std::memory_order_relaxed);
452 m_freeCount += 1;
454 l.unlock();
455 m_compilerCv.notify_one();
458 void CompilerPool::start() {
459 auto const nworkers = m_options.workers;
460 m_freeCount.store(nworkers, std::memory_order_relaxed);
461 for (int i = 0; i < nworkers; ++i) {
462 m_compilers[i].store(new ExternCompiler(m_options),
463 std::memory_order_relaxed);
466 CompilerGuard g(*this);
467 m_version = g->getVersionString();
470 void CompilerPool::shutdown(bool detach_compilers) {
471 for (int i = 0; i < m_compilers.size(); ++i) {
472 if (auto c = m_compilers.exchange(i, nullptr)) {
473 if (detach_compilers) {
474 c->detach_from_process();
476 delete c;
482 template<typename F>
483 auto run_compiler(
484 const CompilerGuard& compiler,
485 int maxRetries,
486 bool verboseErrors,
487 F&& func,
488 bool& internal_error) ->
489 boost::variant<
490 typename std::result_of<F&&(const CompilerGuard&)>::type,
491 std::string
494 std::stringstream err;
496 size_t retry = 0;
497 const size_t max = std::max<size_t>(1, maxRetries + 1);
498 while (retry++ < max) {
499 try {
500 internal_error = false;
501 return func(compiler);
502 } catch (FatalErrorException&) {
503 // let these propagate out of the compiler
504 throw;
505 } catch (AssemblerUnserializationError& ex) {
506 // Variable unserializer threw when called from the assembler, treat it
507 // as an internal error.
508 internal_error = true;
509 return ex.what();
510 } catch (AssemblerError& ex) {
511 // Assembler rejected hhas generated by external compiler
512 return ex.what();
513 } catch (CompilerFatal& ex) {
514 // ExternCompiler returned an error when building this unit
515 return ex.what();
516 } catch (AssemblerFatal& ex) {
517 // Assembler returned an error when building this unit
518 return ex.what();
519 } catch (CompileException& ex) {
520 internal_error = true;
521 // Swallow and retry, we return infra errors in bulk once the retry limit
522 // is exceeded.
523 err << ex.what();
524 if (retry < max) err << '\n';
525 } catch (std::runtime_error& ex) {
526 internal_error = true;
527 // Nontransient, don't bother with a retry.
528 return ex.what();
532 if (verboseErrors) {
533 Logger::Error(
534 "ExternCompiler encountered too many communication errors, giving up."
537 return err.str();
540 ParseFactsResult extract_facts_worker(const CompilerGuard& compiler,
541 const std::string& filename,
542 const char* code,
543 int len,
544 int maxRetries,
545 bool verboseErrors
547 auto extract = [&](const CompilerGuard& c) {
548 auto result = c->extract_facts(filename, folly::StringPiece(code, len));
549 return FactsJSONString { result };
551 auto internal_error = false;
552 return run_compiler(
553 compiler,
554 maxRetries,
555 verboseErrors,
556 extract,
557 internal_error);
560 CompilerResult CompilerPool::compile(const char* code,
561 int len,
562 const char* filename,
563 const SHA1& sha1,
564 const Native::FuncTable& nativeFuncs,
565 bool forDebuggerEval,
566 bool wantsSymbolRefs,
567 bool& internal_error,
568 const RepoOptions& options
570 auto compile = [&](const CompilerGuard& c) {
571 return c->compile(filename,
572 sha1,
573 folly::StringPiece(code, len),
574 nativeFuncs,
575 forDebuggerEval,
576 wantsSymbolRefs,
577 options);
579 return run_compiler(
580 CompilerGuard(*this),
581 m_options.maxRetries,
582 m_options.verboseErrors,
583 compile,
584 internal_error);
587 FfpResult CompilerPool::parse(std::string file, const char* code, int len) {
588 auto compile = [&](const CompilerGuard& c) {
589 auto result = c->ffp_parse_file(file, folly::StringPiece(code, len));
590 return FfpJSONString { result };
592 auto internal_error = false;
593 return run_compiler(
594 CompilerGuard(*this),
595 m_options.maxRetries,
596 m_options.verboseErrors,
597 compile,
598 internal_error);
601 ////////////////////////////////////////////////////////////////////////////////
603 std::string readline(FILE* f) {
604 char* line = nullptr;
605 size_t mx = 0;
606 ssize_t len = 0;
607 SCOPE_EXIT { free(line); };
609 for (auto tries = 0; tries < 10; tries++) {
610 if ((len = getline(&line, &mx, f)) >= 0) {
611 break;
613 if (errno == EINTR) {
614 // Signal. Maybe Xenon? Just try again within reason.
615 ::clearerr(f);
616 continue;
618 // Non-EINTR error.
619 break;
622 if (len < 0) {
623 throwErrno("error reading line");
626 return len ? std::string(line, len - 1) : std::string();
629 std::string ExternCompiler::readVersion() const {
630 // Note the utter lack of error handling. We're really expecting the version
631 // JSON to be the first thing we get from the compiler daemon, and that it has
632 // a "version" field, and that the value at the field is indeed a string...
633 const auto line = readline(m_out);
634 return folly::parseJson(line).at("version").asString();
637 std::string ExternCompiler::readResult(StructuredLogEntry* log) const {
638 const auto line = readline(m_out);
639 const auto header = folly::parseJson(line);
640 const std::string type = header.getDefault("type", "").asString();
641 const std::size_t bytes = header.getDefault("bytes", 0).asInt();
643 const auto logResult = [&] (auto name, auto t) {
644 if (log != nullptr) log->setInt(name, t);
645 FTRACE(2, "{} took {} us\n", name, t);
648 if (RuntimeOption::EvalLogExternCompilerPerf) {
649 if (auto parsing_time = header.get_ptr("parsing_time")) {
650 logResult("extern_parsing", parsing_time->asInt());
652 if (auto codegen_time = header.get_ptr("codegen_time")) {
653 logResult("extern_codegen", codegen_time->asInt());
655 if (auto printing_time = header.get_ptr("printing_time")) {
656 logResult("extern_printing", printing_time->asInt());
660 if (type == "success") {
661 std::string program(bytes, '\0');
662 if (bytes != 0 && fread(&program[0], bytes, 1, m_out) != 1) {
663 throwErrno("reading input program");
665 return program;
666 } else if (type == "error") {
667 // We don't need to restart the pipe -- the compiler just wasn't able to
668 // build this file...
669 throw CompilerFatal(
670 header.getDefault("error", "[no 'error' field]").asString());
671 } else {
672 throw CompilerFatal("unknown message type, " + type);
675 not_reached();
678 void ExternCompiler::stopLogStderrThread() {
679 SCOPE_EXIT { m_err = nullptr; };
680 if (m_err) {
681 fclose(m_err); // need to unblock getline()
683 if (m_logStderrThread && m_logStderrThread->joinable()) {
684 m_logStderrThread->join();
688 void ExternCompiler::writeMessage(
689 folly::dynamic& header,
690 folly::StringPiece body
692 const auto bytes = body.size();
693 header["bytes"] = bytes;
694 const auto jsonHeader = folly::toJson(header);
695 if (
696 fprintf(m_in, "%s\n", jsonHeader.data()) == -1 ||
697 (bytes > 0 && fwrite(body.begin(), bytes, 1, m_in) != 1)
699 throwErrno("error writing message");
701 fflush(m_in);
704 struct ConfigBuilder {
705 template<typename T>
706 ConfigBuilder& addField(folly::StringPiece key, const T& data) {
707 if (!m_config.isObject()) {
708 m_config = folly::dynamic::object();
711 m_config[key] = folly::dynamic::object(
712 "global_value", folly::toDynamic(data));
714 return *this;
717 std::string toString() const {
718 return m_config.isNull() ? "" : folly::toJson(m_config);
721 private:
722 folly::dynamic m_config{nullptr};
725 void ExternCompiler::writeConfigs() {
726 static const std::string boundConfig = [this] () -> std::string {
727 if (m_options.inheritConfig) {
728 // necessary to initialize zend-strtod, which is used to serialize
729 // boundConfig to JSON (!)
730 zend_get_bigint_data();
731 return IniSetting::GetAllAsJSON();
733 return "";
734 }();
736 // Some configs, like IncludeRoots, can't easily be Config::Bind(ed), so here
737 // we create a place to dump miscellaneous config values HackC might want.
738 static const std::string miscConfig = [this] () -> std::string {
739 if (m_options.inheritConfig) {
740 return ConfigBuilder()
741 .addField("hhvm.include_roots", RuntimeOption::IncludeRoots)
742 .toString();
744 return "";
745 }();
747 folly::dynamic header = folly::dynamic::object("type", "config");
748 writeMessage(header, boundConfig);
749 writeMessage(header, miscConfig);
752 void ExternCompiler::writeProgram(
753 const char* filename,
754 SHA1 sha1,
755 folly::StringPiece code,
756 bool forDebuggerEval,
757 const RepoOptions& options
759 folly::dynamic header = folly::dynamic::object
760 ("type", "code")
761 ("sha1", sha1.toString())
762 ("file", filename)
763 ("is_systemlib", !SystemLib::s_inited)
764 ("for_debugger_eval", forDebuggerEval)
765 ("config_overrides", options.toDynamic())
766 ("root", SourceRootInfo::GetCurrentSourceRoot());
767 writeMessage(header, code);
770 void ExternCompiler::writeExtractFacts(
771 const std::string& filename,
772 folly::StringPiece code
774 folly::dynamic header = folly::dynamic::object
775 ("type", "facts")
776 ("file", filename)
777 ("root", SourceRootInfo::GetCurrentSourceRoot());
778 writeMessage(header, code);
781 void ExternCompiler::writeParseFile(
782 const std::string& filename,
783 folly::StringPiece code
785 folly::dynamic header = folly::dynamic::object
786 ("type", "parse")
787 ("file", filename);
788 writeMessage(header, code);
791 struct CompilerManager final {
792 int get_delegate() { return m_delegate; }
793 std::mutex& get_delegate_lock() { return m_delegateLock; }
794 void set_username(const std::string& username) { m_username = username; }
795 void ensure_started();
796 void shutdown();
797 void detach_after_fork();
798 CompilerPool& get_hackc_pool();
799 private:
800 void stop(bool detach_compilers);
801 int m_delegate{kInvalidPid};
802 std::mutex m_delegateLock;
804 std::unique_ptr<CompilerPool> m_hackc_pool;
806 std::atomic<bool> m_started{false};
807 std::mutex m_compilers_start_lock;
808 folly::Optional<std::string> m_username;
809 } s_manager;
811 struct UseLightDelegate final {
812 UseLightDelegate()
813 : m_lock(s_manager.get_delegate_lock())
814 , m_prev(LightProcess::setThreadLocalAfdtOverride(s_manager.get_delegate()))
817 UseLightDelegate(UseLightDelegate&&) = delete;
818 UseLightDelegate& operator=(UseLightDelegate&&) = delete;
820 ~UseLightDelegate() {
821 LightProcess::setThreadLocalAfdtOverride(std::move(m_prev));
823 private:
824 std::unique_lock<std::mutex> m_lock;
825 std::unique_ptr<LightProcess> m_prev;
828 void ExternCompiler::stop() {
829 // This is super-gross: it's possible we're in a forked child -- but fork()
830 // doesn't -- can't -- copy over threads, so m_logStderrThread is rubbish --
831 // but joinable() in the child. When the child's ~ExternCompiler() destructor
832 // is called, it will call m_logStderrThread's destructor, terminating a
833 // joinable but unjoined thread, which causes a panic. We really shouldn't be
834 // mixing threads with forking, but we should just about get away with it
835 // here.
836 SCOPE_EXIT {
837 stopLogStderrThread();
840 if (m_pid == kInvalidPid) return;
842 SCOPE_EXIT {
843 // We must close err before in, otherwise there's a race:
844 // - hackc tries to read from stdin
845 // - stdin is closed
846 // - hackc writes an error to stderr
847 // - the error handler thread in HHVM spews out the error
848 // This makes the tests unrunnable, but probably doesn't have a practical
849 // effect on real usage other than log spew on process shutdown.
850 if (m_err) fclose(m_err);
851 if (m_in) fclose(m_in);
852 if (m_out) fclose(m_out);
853 m_err = m_in = m_out = nullptr;
854 m_pid = kInvalidPid;
857 m_compilations = 0;
859 auto ret = kill(m_pid, SIGTERM);
860 if (ret == -1) {
861 Logger::FWarning(
862 "ExternCompiler: kill failed: {}, {}",
863 errno,
864 folly::errnoStr(errno).c_str());
867 int status, code;
869 UseLightDelegate useDelegate;
870 ret = LightProcess::waitpid(m_pid, &status, 0, 2);
871 if (ret != m_pid) {
872 Logger::FWarning(
873 "ExternCompiler: unable to wait for compiler process, return code {},"
874 "errno: {}, {}",
875 ret,
876 errno,
877 folly::errnoStr(errno).c_str());
878 return;
882 if (WIFEXITED(status) && (code = WEXITSTATUS(status)) != 0) {
883 Logger::FWarning("ExternCompiler: exited with status code {}", code);
884 } else if (WIFSIGNALED(status) && (code = WTERMSIG(status)) != SIGTERM) {
885 Logger::FWarning(
886 "ExternCompiler: terminated by signal {}{}",
887 code,
888 WCOREDUMP(status) ? " (code dumped)" : ""
893 struct Pipe final {
894 Pipe() {
895 if (pipe2(fds, O_CLOEXEC) == -1) throwErrno("unable to open pipe");
897 ~Pipe() {
898 if (fds[0] != -1) close(fds[0]);
899 if (fds[1] != -1) close(fds[1]);
901 FILE* detach(const char* mode) {
902 auto ret = fdopen(fds[*mode == 'r' ? 0 : 1], mode);
903 if (!ret) throwErrno("unable to fdopen pipe");
904 close(fds[*mode == 'r' ? 1 : 0]);
905 fds[0] = fds[1] = -1;
906 return ret;
908 int remoteIn() const { return fds[0]; }
909 int remoteOut() const { return fds[1]; }
910 int fds[2];
913 void ExternCompiler::start() {
914 if (m_pid != kInvalidPid) return;
916 Pipe in, out, err;
917 std::vector<int> created = {in.remoteIn(), out.remoteOut(), err.remoteOut()};
918 std::vector<int> wanted = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
919 std::vector<std::string> env;
921 auto const command = hackcCommand();
923 if (!command.empty()) {
924 UseLightDelegate useDelegate;
926 m_pid = LightProcess::proc_open(
927 command.c_str(),
928 created,
929 wanted,
930 nullptr /* cwd */,
933 } else {
934 Logger::Error("Unable to get external command");
935 throw BadCompilerException("Unable to get external command");
938 if (m_pid == kInvalidPid) {
939 const auto msg = folly::to<std::string>(
940 "Unable to start external compiler with command: ", command.c_str());
941 Logger::Error(msg);
942 throw BadCompilerException(msg);
945 m_in = in.detach("w");
946 m_out = out.detach("r");
947 m_err = err.detach("r");
949 m_logStderrThread = std::make_unique<std::thread>([&]() {
950 int ret = 0;
951 auto pid = m_pid;
952 try {
953 pollfd pfd[] = {{fileno(m_err), POLLIN, 0}};
954 while ((ret = poll(pfd, 1, -1)) != -1) {
955 if (ret == 0) continue;
956 if (pfd[0].revents & (POLLHUP | POLLNVAL | POLLERR)) {
957 throw std::runtime_error("hangup");
959 if (pfd[0].revents) {
960 const auto line = readline(m_err);
961 Logger::FError("[external compiler {}]: {}", pid, line);
964 } catch (const std::exception& exc) {
965 // The stderr output messes with expected test output, which presumably
966 // come from non-server runs.
967 if (RuntimeOption::ServerMode) {
968 Logger::FVerbose(
969 "Ceasing to log stderr from external compiler ({}): {}",
970 pid,
971 exc.what());
976 // Here we expect the very first communication from the external compiler
977 // process to be a single line of JSON representing the compiler version.
978 try {
979 m_version = readVersion();
980 } catch (const CompileException& exc) {
981 throw BadCompilerException(
982 "Couldn't read version message from external compiler");
985 // For...reasons...the external compiler process misses the first line of
986 // output on the pipe, so we open communications with a single newline.
987 if (fprintf(m_in, "\n") == -1) {
988 throw BadCompilerException("Couldn't write initial newline");
990 fflush(m_in);
992 writeConfigs();
995 CompilerResult hackc_compile(
996 const char* code,
997 int len,
998 const char* filename,
999 const SHA1& sha1,
1000 const Native::FuncTable& nativeFuncs,
1001 bool forDebuggerEval,
1002 bool wantsSymbolRefs,
1003 bool& internal_error,
1004 const RepoOptions& options
1006 return s_manager.get_hackc_pool().compile(
1007 code,
1008 len,
1009 filename,
1010 sha1,
1011 nativeFuncs,
1012 forDebuggerEval,
1013 wantsSymbolRefs,
1014 internal_error,
1015 options
1019 ////////////////////////////////////////////////////////////////////////////////
1022 void CompilerManager::ensure_started() {
1023 if (m_started.load(std::memory_order_acquire)) {
1024 return;
1026 std::unique_lock<std::mutex> l(m_compilers_start_lock);
1027 if (m_started.load(std::memory_order_relaxed)) {
1028 return;
1031 m_delegate = LightProcess::createDelegate();
1032 if (m_delegate != kInvalidPid && m_username) {
1033 std::unique_lock<std::mutex> lock(m_delegateLock);
1034 LightProcess::ChangeUser(m_delegate, m_username.value());
1037 CompilerOptions hackcConfig {
1038 RuntimeOption::EvalHackCompilerVerboseErrors,
1039 RuntimeOption::EvalHackCompilerMaxRetries,
1040 RuntimeOption::EvalHackCompilerWorkers,
1041 RuntimeOption::EvalHackCompilerInheritConfig,
1043 m_hackc_pool = std::make_unique<CompilerPool>(std::move(hackcConfig));
1044 m_hackc_pool->start();
1046 m_started.store(true, std::memory_order_release);
1049 void CompilerManager::stop(bool detach_compilers) {
1050 if (m_hackc_pool) {
1051 m_hackc_pool->shutdown(detach_compilers);
1052 m_hackc_pool = nullptr;
1055 close(m_delegate);
1056 m_delegate = kInvalidPid;
1057 m_started.store(false, std::memory_order_relaxed);
1060 void CompilerManager::shutdown() {
1061 stop(false);
1064 void CompilerManager::detach_after_fork() {
1065 stop(true);
1068 CompilerPool& CompilerManager::get_hackc_pool() {
1069 ensure_started();
1070 return *m_hackc_pool;
1073 void compilers_start() {
1074 s_manager.ensure_started();
1075 #if FOLLY_HAVE_PTHREAD_ATFORK
1076 pthread_atfork(
1077 nullptr /* prepare */,
1078 nullptr /* parent */,
1079 compilers_detach_after_fork /* child */
1081 #endif
1084 void compilers_set_user(const std::string& username) {
1085 s_manager.set_username(username);
1088 void compilers_shutdown() {
1089 s_manager.shutdown();
1090 std::unique_lock<std::mutex> lock{s_extractLock};
1091 if (!s_extractPath.empty() &&
1092 s_extractPath != RuntimeOption::EvalHackCompilerExtractPath) {
1093 unlink(s_extractPath.data());
1097 void compilers_detach_after_fork() {
1098 s_manager.detach_after_fork();
1099 std::unique_lock<std::mutex> lock{s_extractLock};
1100 if (!s_extractPath.empty() &&
1101 s_extractPath != RuntimeOption::EvalHackCompilerExtractPath) {
1102 s_extractPath.clear();
1106 std::unique_ptr<FactsParser> acquire_facts_parser() {
1107 return std::make_unique<CompilerGuard>(s_manager.get_hackc_pool());
1110 ParseFactsResult extract_facts(
1111 const FactsParser& facts_parser,
1112 const std::string& filename,
1113 const char* code,
1114 int len
1116 size_t maxRetries;
1117 bool verboseErrors;
1118 std::tie(maxRetries, verboseErrors) =
1119 s_manager.get_hackc_pool().getMaxRetriesAndVerbosity();
1120 return extract_facts_worker(
1121 dynamic_cast<const CompilerGuard&>(facts_parser),
1122 filename,
1123 code,
1124 len,
1125 maxRetries,
1126 verboseErrors);
1129 FfpResult ffp_parse_file(std::string file, const char *contents, int size) {
1130 return s_manager.get_hackc_pool().parse(file, contents, size);
1134 std::string hackc_version() {
1135 return s_manager.get_hackc_pool().getVersionString();
1138 bool startsWith(const char* big, const char* small) {
1139 return strncmp(big, small, strlen(small)) == 0;
1142 bool isFileHack(const char* code, size_t codeLen) {
1143 // if the file starts with a shebang
1144 if (codeLen > 2 && strncmp(code, "#!", 2) == 0) {
1145 // reset code to the next char after the shebang line
1146 const char* loc = reinterpret_cast<const char*>(
1147 memchr(code, '\n', codeLen));
1148 if (!loc) {
1149 return false;
1152 ptrdiff_t offset = loc - code;
1153 code = loc + 1;
1154 codeLen -= offset + 1;
1157 return codeLen > strlen("<?hh") && startsWith(code, "<?hh");
1160 std::unique_ptr<UnitCompiler>
1161 UnitCompiler::create(const char* code,
1162 int codeLen,
1163 const char* filename,
1164 const SHA1& sha1,
1165 const Native::FuncTable& nativeFuncs,
1166 bool forDebuggerEval,
1167 const RepoOptions& options
1169 s_manager.ensure_started();
1170 return std::make_unique<HackcUnitCompiler>(
1171 code,
1172 codeLen,
1173 filename,
1174 sha1,
1175 nativeFuncs,
1176 forDebuggerEval,
1177 options
1181 std::unique_ptr<UnitEmitter> HackcUnitCompiler::compile(
1182 bool wantsSymbolRefs) const {
1183 bool ice = false;
1184 auto res = hackc_compile(m_code,
1185 m_codeLen,
1186 m_filename,
1187 m_sha1,
1188 m_nativeFuncs,
1189 m_forDebuggerEval,
1190 wantsSymbolRefs,
1191 ice,
1192 m_options);
1193 std::unique_ptr<UnitEmitter> unitEmitter;
1194 match<void>(
1195 res,
1196 [&] (std::unique_ptr<UnitEmitter>& ue) {
1197 unitEmitter = std::move(ue);
1199 [&] (std::string& err) {
1200 unitEmitter = createFatalUnit(
1201 makeStaticString(m_filename),
1202 m_sha1,
1203 FatalOp::Runtime,
1204 makeStaticString(err));
1208 if (unitEmitter) unitEmitter->m_ICE = ice;
1209 return unitEmitter;
1212 ////////////////////////////////////////////////////////////////////////////////