2 +----------------------------------------------------------------------+
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"
20 #include <condition_variable>
25 #include <sys/types.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"
52 TRACE_SET_MOD(extern_compiler
);
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
);
60 SCOPE_EXIT
{ close(fd
); };
62 if (folly::readFile(fd
, contents
) && contents
== binary
) return true;
66 folly::writeFileAtomic(path
, binary
, 0755);
67 } catch (std::system_error
& ex
) {
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
) {
96 *tl_extractPath
.getCheck() = 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
);
107 if (!get_embedded_data("hackc_binary", &desc
)) {
108 Logger::Error("Embedded hackc binary is missing");
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");
121 auto const binary
= std::string(bin_str
, len
);
122 if (createHackc(location
, binary
)) return set(location
);
125 SCOPE_EXIT
{ if (fd
!= -1) close(fd
); };
127 auto fallback
= RuntimeOption::EvalHackCompilerFallbackPath
;
128 if ((fd
= mkstemp(&fallback
[0])) == -1) {
130 "Unable to create temp file for hackc binary: {}", folly::errnoStr(errno
)
135 if (folly::writeFull(fd
, binary
.data(), binary
.size()) == -1) {
137 "Failed to write extern hackc binary: {}", folly::errnoStr(errno
)
142 if (chmod(fallback
.data(), 0755) != 0) {
143 Logger::Error("Unable to mark hackc binary as writable");
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
) {}
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
{
182 constexpr int kInvalidPid
= -1;
184 struct ExternCompiler
{
185 explicit ExternCompiler(const CompilerOptions
& 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
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
197 m_logStderrThread
.release();
199 ~ExternCompiler() { stop(); }
201 std::string
extract_facts(
202 const std::string
& filename
,
203 folly::StringPiece code
210 writeExtractFacts(filename
, code
);
211 return readResult(nullptr /* structured log entry */);
213 catch (CompileException
& ex
) {
215 if (m_options
.verboseErrors
) {
216 Logger::FError("ExternCompiler Error (facts): {}", ex
.what());
222 std::string
ffp_parse_file(
223 const std::string
& filename
,
224 folly::StringPiece code
230 writeParseFile(filename
, code
);
231 return readResult(nullptr /* structured log entry */);
233 catch (CompileException
& ex
) {
235 if (m_options
.verboseErrors
) {
236 Logger::FError("ExternCompiler Error (parse): {}", ex
.what());
243 StructuredLogEntry
& log
,
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
);
257 std::unique_ptr
<UnitEmitter
> compile(
258 const char* filename
,
260 folly::StringPiece code
,
261 const Native::FuncTable
& nativeFuncs
,
262 bool forDebuggerEval
,
263 bool wantsSymbolRefs
,
264 const RepoOptions
& options
271 std::unique_ptr
<Unit
> u
;
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(),
286 false /* swallow errors */,
289 logTime(log
, t
, "assemble_hhas");
290 if (RuntimeOption::EvalLogExternCompilerPerf
) {
291 StructuredLog::log("hhvm_detailed_frontend_performance", log
);
294 } catch (CompileException
& ex
) {
296 if (m_options
.verboseErrors
) {
297 Logger::FError("ExternCompiler Error: {}", ex
.what());
300 } catch (CompilerFatal
& ex
) {
301 // this catch is here so we don't fall into the std::runtime_error one
303 } catch (AssemblerFatal
& ex
) {
304 // this catch is here so we don't fall into the std::runtime_error one
306 } catch (FatalErrorException
&) {
307 // we want these to propagate out of the compiler
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.
313 } catch (AssemblerError
& ex
) {
314 if (m_options
.verboseErrors
) {
315 auto const msg
= folly::sformat(
317 "========== PHP Source ==========\n"
319 "========== ExternCompiler Result ==========\n"
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
);
331 } catch (std::runtime_error
& ex
) {
332 if (m_options
.verboseErrors
) {
333 Logger::FError("ExternCompiler Runtime Error: {}", ex
.what());
339 std::string
getVersionString() {
340 if (!isRunning()) start();
347 bool isRunning() const { return m_pid
!= kInvalidPid
; }
348 void stopLogStderrThread();
350 void writeMessage(folly::dynamic
& header
, folly::StringPiece body
);
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
};
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
)
377 , m_compilers(options
.workers
, nullptr)
380 std::pair
<size_t, ExternCompiler
*> getCompiler();
381 void releaseCompiler(size_t id
, ExternCompiler
* ptr
);
383 void shutdown(bool detach_compilers
);
384 CompilerResult
compile(const char* code
,
386 const char* filename
,
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
,
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
);
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
)
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
; }
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;
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
);
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
);
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();
484 const CompilerGuard
& compiler
,
488 bool& internal_error
) ->
490 typename
std::result_of
<F
&&(const CompilerGuard
&)>::type
,
494 std::stringstream err
;
497 const size_t max
= std::max
<size_t>(1, maxRetries
+ 1);
498 while (retry
++ < max
) {
500 internal_error
= false;
501 return func(compiler
);
502 } catch (FatalErrorException
&) {
503 // let these propagate out of the compiler
505 } catch (AssemblerUnserializationError
& ex
) {
506 // Variable unserializer threw when called from the assembler, treat it
507 // as an internal error.
508 internal_error
= true;
510 } catch (AssemblerError
& ex
) {
511 // Assembler rejected hhas generated by external compiler
513 } catch (CompilerFatal
& ex
) {
514 // ExternCompiler returned an error when building this unit
516 } catch (AssemblerFatal
& ex
) {
517 // Assembler returned an error when building this unit
519 } catch (CompileException
& ex
) {
520 internal_error
= true;
521 // Swallow and retry, we return infra errors in bulk once the retry limit
524 if (retry
< max
) err
<< '\n';
525 } catch (std::runtime_error
& ex
) {
526 internal_error
= true;
527 // Nontransient, don't bother with a retry.
534 "ExternCompiler encountered too many communication errors, giving up."
540 ParseFactsResult
extract_facts_worker(const CompilerGuard
& compiler
,
541 const std::string
& filename
,
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;
560 CompilerResult
CompilerPool::compile(const char* code
,
562 const char* filename
,
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
,
573 folly::StringPiece(code
, len
),
580 CompilerGuard(*this),
581 m_options
.maxRetries
,
582 m_options
.verboseErrors
,
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;
594 CompilerGuard(*this),
595 m_options
.maxRetries
,
596 m_options
.verboseErrors
,
601 ////////////////////////////////////////////////////////////////////////////////
603 std::string
readline(FILE* f
) {
604 char* line
= nullptr;
607 SCOPE_EXIT
{ free(line
); };
609 for (auto tries
= 0; tries
< 10; tries
++) {
610 if ((len
= getline(&line
, &mx
, f
)) >= 0) {
613 if (errno
== EINTR
) {
614 // Signal. Maybe Xenon? Just try again within reason.
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");
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...
670 header
.getDefault("error", "[no 'error' field]").asString());
672 throw CompilerFatal("unknown message type, " + type
);
678 void ExternCompiler::stopLogStderrThread() {
679 SCOPE_EXIT
{ m_err
= nullptr; };
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
);
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");
704 struct ConfigBuilder
{
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
));
717 std::string
toString() const {
718 return m_config
.isNull() ? "" : folly::toJson(m_config
);
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();
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
)
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
,
755 folly::StringPiece code
,
756 bool forDebuggerEval
,
757 const RepoOptions
& options
759 folly::dynamic header
= folly::dynamic::object
761 ("sha1", sha1
.toString())
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
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
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();
797 void detach_after_fork();
798 CompilerPool
& get_hackc_pool();
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
;
811 struct UseLightDelegate final
{
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
));
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
837 stopLogStderrThread();
840 if (m_pid
== kInvalidPid
) return;
843 // We must close err before in, otherwise there's a race:
844 // - hackc tries to read from stdin
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;
859 auto ret
= kill(m_pid
, SIGTERM
);
862 "ExternCompiler: kill failed: {}, {}",
864 folly::errnoStr(errno
).c_str());
869 UseLightDelegate useDelegate
;
870 ret
= LightProcess::waitpid(m_pid
, &status
, 0, 2);
873 "ExternCompiler: unable to wait for compiler process, return code {},"
877 folly::errnoStr(errno
).c_str());
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
) {
886 "ExternCompiler: terminated by signal {}{}",
888 WCOREDUMP(status
) ? " (code dumped)" : ""
895 if (pipe2(fds
, O_CLOEXEC
) == -1) throwErrno("unable to open 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;
908 int remoteIn() const { return fds
[0]; }
909 int remoteOut() const { return fds
[1]; }
913 void ExternCompiler::start() {
914 if (m_pid
!= kInvalidPid
) return;
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(
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());
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
>([&]() {
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
) {
969 "Ceasing to log stderr from external compiler ({}): {}",
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.
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");
995 CompilerResult
hackc_compile(
998 const char* filename
,
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(
1019 ////////////////////////////////////////////////////////////////////////////////
1022 void CompilerManager::ensure_started() {
1023 if (m_started
.load(std::memory_order_acquire
)) {
1026 std::unique_lock
<std::mutex
> l(m_compilers_start_lock
);
1027 if (m_started
.load(std::memory_order_relaxed
)) {
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
) {
1051 m_hackc_pool
->shutdown(detach_compilers
);
1052 m_hackc_pool
= nullptr;
1056 m_delegate
= kInvalidPid
;
1057 m_started
.store(false, std::memory_order_relaxed
);
1060 void CompilerManager::shutdown() {
1064 void CompilerManager::detach_after_fork() {
1068 CompilerPool
& CompilerManager::get_hackc_pool() {
1070 return *m_hackc_pool
;
1073 void compilers_start() {
1074 s_manager
.ensure_started();
1075 #if FOLLY_HAVE_PTHREAD_ATFORK
1077 nullptr /* prepare */,
1078 nullptr /* parent */,
1079 compilers_detach_after_fork
/* child */
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
,
1118 std::tie(maxRetries
, verboseErrors
) =
1119 s_manager
.get_hackc_pool().getMaxRetriesAndVerbosity();
1120 return extract_facts_worker(
1121 dynamic_cast<const CompilerGuard
&>(facts_parser
),
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
));
1152 ptrdiff_t offset
= loc
- code
;
1154 codeLen
-= offset
+ 1;
1157 return codeLen
> strlen("<?hh") && startsWith(code
, "<?hh");
1160 std::unique_ptr
<UnitCompiler
>
1161 UnitCompiler::create(const char* code
,
1163 const char* filename
,
1165 const Native::FuncTable
& nativeFuncs
,
1166 bool forDebuggerEval
,
1167 const RepoOptions
& options
1169 s_manager
.ensure_started();
1170 return std::make_unique
<HackcUnitCompiler
>(
1181 std::unique_ptr
<UnitEmitter
> HackcUnitCompiler::compile(
1182 bool wantsSymbolRefs
) const {
1184 auto res
= hackc_compile(m_code
,
1193 std::unique_ptr
<UnitEmitter
> unitEmitter
;
1196 [&] (std::unique_ptr
<UnitEmitter
>& ue
) {
1197 unitEmitter
= std::move(ue
);
1199 [&] (std::string
& err
) {
1200 unitEmitter
= createFatalUnit(
1201 makeStaticString(m_filename
),
1204 makeStaticString(err
));
1208 if (unitEmitter
) unitEmitter
->m_ICE
= ice
;
1212 ////////////////////////////////////////////////////////////////////////////////