Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

224 lines
5.7 KiB
C++

// Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
#pragma once
#include <deque>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include <hilti/rt/filesystem.h>
#include <hilti/rt/library.h>
#include <hilti/base/util.h>
#include <hilti/compiler/context.h>
#include <hilti/compiler/detail/cxx/unit.h>
namespace reproc {
class process;
}
namespace hilti {
namespace logging::debug {
inline const DebugStream Jit("jit");
} // namespace logging::debug
namespace detail::jit {
class Cxx;
} // namespace detail::jit
/** Container for C++ code compiled from a HILTI source file */
class CxxCode {
public:
/**
* Reads C++ code from a file.
*
* @param path file to read
*/
CxxCode(const hilti::rt::filesystem::path& path) { load(path); }
/**
* Reads C++ code from an input stream.
*
* @param id name to associate with the input for logging and error messages.
* @param code stream to read from
*/
CxxCode(const std::string& id, std::istream& code) { load(id, code); }
/**
* Initializes code instance from in-memory compiler output. For internal use.
*
* @param u unit to initialize code instance from
*/
explicit CxxCode(const detail::cxx::Unit& u);
/**
* Saves C++ code into a file.
*
* @param p file to write to
* @return true if successful
*/
bool save(const hilti::rt::filesystem::path& p) const;
/**
* Writes C++ code into an output stream.
*
* @param out stream to write to
* @return true if successful
*/
bool save(std::ostream& out) const;
/** Returns C++ code as a string. */
const auto& code() const { return _code; }
/** Returns true if this instance has been initialized with any C++ code. */
auto isLoaded() const { return _code.has_value(); }
/**
* Returns a name associated with the instance's C++ code. If the code
* has been read from a file, that's the path; otherwise the ID specified
* when initialized.
*/
const std::string& id() const { return _id; }
/** Obtain hash for this code. */
std::size_t hash() const { return _hash; }
protected:
/**
* Loads C++ code from a file.
*
* @param path file to read from
* @return true if successful
*/
bool load(const hilti::rt::filesystem::path& path);
/**
* Loads C++ code from an input stream.
*
* @param id name to associate with the input for logging and error messages.
* @param path stream to read from
* @return true if successful
*/
bool load(const std::string& id, std::istream& in);
private:
std::string _id;
std::optional<std::string> _code;
std::size_t _hash = 0;
};
using hilti::rt::Library;
/**
* Just-in-time compiler.
*
* The class provides the entry point for compiling and executing C++ code
* just in time.
*/
class JIT {
public:
/**
* @param context compiler context to use
* @param dump_code if true, save all C++ code into files `dbg.*` for debugging
*/
explicit JIT(const std::shared_ptr<Context>& context, bool dump_code = false);
~JIT();
JIT() = delete;
JIT(const JIT&) = delete;
JIT(JIT&&) noexcept = delete;
JIT& operator=(const JIT&) = delete;
JIT& operator=(JIT&&) noexcept = delete;
/**
* Schedules C++ for just-in-time compilation. This must be called only
* before `jit()`.
*
* @param d C++ code
*/
void add(CxxCode d);
/**
* Schedules C++ for just-in-time compilation. This must be called only
* before `compile()`.
*
* @param d file to read C++ code from
*/
void add(const hilti::rt::filesystem::path& p);
/**
* Returns true if any source files have been added that need to be
* compiled.
*/
bool hasInputs() { return _codes.size() || _files.size(); }
/**
* Compiles and links all scheduled C++ code into a shared library.
*
* @return the compiled library, which will be ready for loading.
*/
Result<std::shared_ptr<const Library>> build();
/** Returns the compiler context in use. */
auto context() const { return _context.lock(); }
/** Returns the compiler options in use. */
auto options() const { return context()->options(); }
private:
// Check if we have a working compiler.
hilti::Result<Nothing> _checkCompiler();
// Compile C++ to object files.
hilti::Result<Nothing> _compile();
// Link object files into shared library.
hilti::Result<std::shared_ptr<const Library>> _link();
// Clean up after compilation.
void _finish();
std::weak_ptr<Context> _context; // global context for options
bool _dump_code; // save all C++ code for debugging
std::vector<hilti::rt::filesystem::path> _files; // all added source files
std::vector<CxxCode> _codes; // all C++ code units to be compiled
std::vector<hilti::rt::filesystem::path> _objects;
struct Job {
std::unique_ptr<reproc::process> process;
std::string stdout_;
std::string stderr_;
void collectOutputs(int events);
};
struct JobRunner {
JobRunner();
using JobID = uint64_t;
Result<JobID> _scheduleJob(const hilti::rt::filesystem::path& cmd, std::vector<std::string> args);
Result<Nothing> _spawnJob();
Result<Nothing> _waitForJobs();
void finish();
using CmdLine = std::vector<std::string>;
std::deque<std::tuple<JobID, CmdLine>> jobs_pending;
JobID job_counter = 0;
std::map<JobID, Job> jobs;
};
JobRunner _runner;
std::size_t _hash = 0;
};
} // namespace hilti