MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ClangInterpreter.cpp
Go to the documentation of this file.
2
3#include "Commentator.hpp"
4
6
7#ifdef MAYAFLUX_COMPILER_MSVC
8#pragma warning(push)
9#pragma warning(disable : 4291) // no matching operator delete found
10#pragma warning(disable : 4805) // unsafe mix of type 'unsigned int' and type 'bool' in operation
11#endif
12
13#include <llvm/ExecutionEngine/Orc/LLJIT.h>
14#include <llvm/ExecutionEngine/Orc/ThreadSafeModule.h>
15
16#include <llvm/Support/TargetSelect.h>
17#include <llvm/TargetParser/Host.h>
18
19#include <clang/AST/Type.h>
20#include <clang/Frontend/CompilerInstance.h>
21#include <clang/Interpreter/Interpreter.h>
22
23#ifdef MAYAFLUX_COMPILER_MSVC
24#pragma warning(pop)
25#endif
26
27#ifdef MAYAFLUX_PLATFORM_MACOS
28#include <dispatch/dispatch.h>
29#endif
30
31#ifdef MAYAFLUX_PLATFORM_WINDOWS
33#endif
34
35namespace Lila {
36
38 std::unique_ptr<clang::Interpreter> interpreter;
39
40 std::vector<std::string> include_paths;
41 std::vector<std::string> compile_flags;
42 std::string target_triple;
43
44 std::unordered_map<std::string, void*> symbol_table;
45 int eval_counter = 0;
46};
47
49 : m_impl(std::make_unique<Impl>())
50{
51 m_impl->target_triple = llvm::sys::getDefaultTargetTriple();
52}
53
58
60ClangInterpreter& ClangInterpreter::operator=(ClangInterpreter&&) noexcept = default;
61
62bool ClangInterpreter::initialize(bool skip_host_library_load)
63{
64 LILA_INFO(Emitter::INTERPRETER, "Initializing Clang interpreter");
65
66#ifdef MAYAFLUX_PLATFORM_WINDOWS
67 llvm::sys::DynamicLibrary::LoadLibraryPermanently("msvcp140.dll");
68 llvm::sys::DynamicLibrary::LoadLibraryPermanently("vcruntime140.dll");
69 llvm::sys::DynamicLibrary::LoadLibraryPermanently("ucrtbase.dll");
70#endif
71
72 llvm::InitializeNativeTarget();
73 llvm::InitializeNativeTargetAsmPrinter();
74 llvm::InitializeNativeTargetAsmParser();
75
76 m_impl->compile_flags.clear();
77 m_impl->compile_flags.emplace_back("-std=c++23");
78 m_impl->compile_flags.emplace_back("-DMAYASIMPLE");
79
80#ifdef MAYAFLUX_PLATFORM_LINUX
81 m_impl->compile_flags.emplace_back("-mcmodel=large");
82 m_impl->compile_flags.emplace_back("-fPIC");
83 m_impl->compile_flags.emplace_back("-fPIE");
84#endif
85
86 std::string pch_dir;
87 if (std::filesystem::exists(MayaFlux::Config::PCH_RUNTIME_PATH)) {
88 pch_dir = MayaFlux::Config::RUNTIME_DATA_DIR;
89 LILA_DEBUG(Emitter::INTERPRETER,
90 std::string("Using installed PCH from: ") + std::string(MayaFlux::Config::PCH_RUNTIME_PATH));
91
92 } else if (std::filesystem::exists(MayaFlux::Config::PCH_SOURCE_PATH)) {
93 pch_dir = std::string(MayaFlux::Config::SOURCE_DIR) + "/cmake";
94 LILA_DEBUG(Emitter::INTERPRETER,
95 std::string("Using source PCH from: ") + std::string(MayaFlux::Config::PCH_SOURCE_PATH));
96
97 } else {
98 m_last_error = "Cannot find pch.h in runtime or source locations";
99 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
100 return false;
101 }
102
103 m_impl->compile_flags.push_back("-I" + pch_dir);
104
105 const std::string& resource_dir = MayaFlux::Platform::SystemConfig::get_clang_resource_dir();
106 if (!resource_dir.empty()) {
107 m_impl->compile_flags.push_back("-resource-dir=" + resource_dir);
108 LILA_DEBUG(Emitter::INTERPRETER,
109 std::string("Using clang resource dir: ") + resource_dir);
110 } else {
111 m_impl->compile_flags.emplace_back("-resource-dir=/usr/lib/clang/21");
112 LILA_WARN(Emitter::INTERPRETER,
113 "Using default clang resource dir: /usr/lib/clang/21");
114 }
115
116 const auto& system_includes = MayaFlux::Platform::SystemConfig::get_system_includes();
117 for (const auto& include : system_includes) {
118 m_impl->compile_flags.push_back("-isystem" + include);
119 }
120
121#ifndef MAYAFLUX_PLATFORM_WINDOWS
122 const auto& eigen_include = MayaFlux::Platform::SystemConfig::get_dep_includes("eigen");
123 if (!eigen_include.empty()) {
124 m_impl->compile_flags.push_back("-isystem" + eigen_include);
125 LILA_DEBUG(Emitter::INTERPRETER,
126 std::string("Added Eigen include path: ") + eigen_include);
127 } else {
128 LILA_WARN(Emitter::INTERPRETER,
129 "Could not find Eigen include path - some features may not work");
130 }
131
132 const auto& freetype_include = MayaFlux::Platform::SystemConfig::get_dep_includes("freetype2");
133 if (!freetype_include.empty()) {
134 m_impl->compile_flags.push_back("-isystem" + freetype_include);
135 LILA_DEBUG(Emitter::INTERPRETER,
136 std::string("Added FreeType include path: ") + freetype_include);
137 } else {
138 LILA_WARN(Emitter::INTERPRETER,
139 "Could not find FreeType include path - some features may not work");
140 }
141#endif
142
143 const std::string magic_enum_include = std::string(MayaFlux::Config::MAGIC_ENUM_INCLUDE);
144 if (!magic_enum_include.empty()) {
145 m_impl->compile_flags.push_back("-I" + magic_enum_include);
146 LILA_DEBUG(Emitter::INTERPRETER,
147 std::string("Added magic_enum include path: ") + magic_enum_include);
148 } else {
149 LILA_WARN(Emitter::INTERPRETER,
150 "Could not find magic_enum include path - some features may not work");
151 }
152
153#ifdef MAYAFLUX_PLATFORM_MACOS
154 // CRITICAL: JIT uses Homebrew LLVM but needs macOS SDK for system headers
155 // Homebrew LLVM's libc++ requires pthread.h, sched.h, time.h, etc. from SDK
156 std::string sdk_path = MayaFlux::Platform::SystemConfig::get_macos_sdk_path();
157 if (!sdk_path.empty()) {
158 m_impl->compile_flags.push_back("-isysroot" + sdk_path);
159 LILA_DEBUG(Emitter::INTERPRETER, "Using macOS SDK: " + sdk_path);
160 } else {
161 LILA_WARN(Emitter::INTERPRETER,
162 "Could not find macOS SDK - JIT may fail to find system headers");
163 }
164#endif
165
166 for (const auto& path : m_impl->include_paths) {
167 m_impl->compile_flags.push_back("-I" + path);
168 }
169
170#ifdef MAYAFLUX_PLATFORM_WINDOWS
171 m_impl->compile_flags.emplace_back("-fno-function-sections");
172 m_impl->compile_flags.emplace_back("-fno-data-sections");
173 m_impl->compile_flags.emplace_back("-fno-unique-section-names");
174 m_impl->compile_flags.emplace_back("-D_CRT_SECURE_NO_WARNINGS");
175#endif
176
177 std::vector<const char*> args;
178 for (const auto& flag : m_impl->compile_flags) {
179 args.push_back(flag.c_str());
180 }
181
182 clang::IncrementalCompilerBuilder ICB;
183 ICB.SetCompilerArgs(args);
184
185 auto CI = ICB.CreateCpp();
186 if (!CI) {
187 m_last_error = "Failed to create CompilerInstance: " + llvm::toString(CI.takeError());
188 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
189 return false;
190 }
191
192 auto interp = clang::Interpreter::create(std::move(*CI));
193 if (!interp) {
194 m_last_error = "Failed to create interpreter: " + llvm::toString(interp.takeError());
195 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
196 return false;
197 }
198
199 m_impl->interpreter = std::move(*interp);
200
201 LILA_INFO(Emitter::INTERPRETER, "Clang interpreter created successfully");
202
203 if (skip_host_library_load) {
204 LILA_DEBUG(Emitter::INTERPRETER,
205 "Skipping MayaFluxLib load: host process already resident");
206 } else {
208 "MayaFluxLib", std::string(MayaFlux::Config::INSTALL_PREFIX));
209
210 LILA_DEBUG(Emitter::INTERPRETER, "Loading MayaFlux library: " + lib_to_load);
211
212 if (auto err = m_impl->interpreter->LoadDynamicLibrary(lib_to_load.c_str())) {
213 m_last_error = "Failed to load " + lib_to_load + ": "
214 + llvm::toString(std::move(err));
215 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
216 return false;
217 }
218
219 LILA_DEBUG(Emitter::INTERPRETER, "Loaded " + lib_to_load);
220 }
221
222 const char* header_include = "#include \"pch.h\"\n"
223 "#include \"MayaFlux/MayaFlux.hpp\"\n";
224
225 auto result = m_impl->interpreter->ParseAndExecute(header_include);
226
227 if (result) {
228 std::string warning = "Failed to load MayaFlux headers: " + llvm::toString(std::move(result));
229 LILA_WARN(Emitter::INTERPRETER, warning);
230 } else {
231 LILA_INFO(Emitter::INTERPRETER, "MayaFlux headers loaded successfully");
232 }
233
234 result = m_impl->interpreter->ParseAndExecute("std::cout << \"Ready for Live\" << std::flush;");
235
236 return true;
237}
238
240{
241#ifdef MAYAFLUX_PLATFORM_MACOS
242 __block EvalResult result;
243#else
244 EvalResult result;
245#endif
246
247 if (!m_impl->interpreter) {
248 result.error = "Interpreter not initialized";
249 LILA_ERROR(Emitter::INTERPRETER, result.error);
250 return result;
251 }
252
253 LILA_DEBUG(Emitter::INTERPRETER, "Evaluating code...");
254
255#ifdef MAYAFLUX_PLATFORM_MACOS
256 dispatch_sync(dispatch_get_main_queue(), ^{
257 auto eval_result = m_impl->interpreter->ParseAndExecute(code);
258
259 if (!eval_result) {
260 result.success = true;
261 LILA_DEBUG(Emitter::INTERPRETER, "Code evaluation succeeded");
262 } else {
263 result.success = false;
264 result.error = "Execution failed: " + llvm::toString(std::move(eval_result));
265 LILA_ERROR(Emitter::INTERPRETER, result.error);
266 }
267 });
268
269#else
270 auto eval_result = m_impl->interpreter->ParseAndExecute(code);
271
272 if (!eval_result) {
273 result.success = true;
274 LILA_DEBUG(Emitter::INTERPRETER, "Code evaluation succeeded");
275 } else {
276 result.success = false;
277 result.error = "Execution failed: " + llvm::toString(std::move(eval_result));
278 LILA_ERROR(Emitter::INTERPRETER, result.error);
279 }
280#endif
281
282 m_impl->eval_counter++;
283 return result;
284}
285
287{
288 EvalResult result;
289
290 if (!std::filesystem::exists(filepath)) {
291 result.error = "File does not exist: " + filepath;
292 LILA_ERROR(Emitter::INTERPRETER, result.error);
293 return result;
294 }
295
296 LILA_INFO(Emitter::INTERPRETER, std::string("Evaluating file: ") + filepath);
297
298 std::string code = "#include \"" + filepath + "\"\n";
299 return eval(code);
300}
301
302void* ClangInterpreter::get_symbol_address(const std::string& name)
303{
304 if (!m_impl->interpreter) {
305 LILA_WARN(Emitter::INTERPRETER, "Cannot get symbol: interpreter not initialized");
306 return nullptr;
307 }
308
309 auto symbol = m_impl->interpreter->getSymbolAddress(name);
310 if (symbol) {
311 LILA_DEBUG(Emitter::INTERPRETER, std::string("Found symbol: ") + name);
312 return reinterpret_cast<void*>(symbol->getValue());
313 }
314
315 LILA_WARN(Emitter::INTERPRETER, std::string("Symbol not found: ") + name);
316 return nullptr;
317}
318
320{
321 std::vector<std::string> symbols;
322 for (const auto& pair : m_impl->symbol_table) {
323 symbols.push_back(pair.first);
324 }
325 return symbols;
326}
327
328void ClangInterpreter::add_include_path(const std::string& path)
329{
330 if (std::filesystem::exists(path)) {
331 m_impl->include_paths.push_back(path);
332 LILA_DEBUG(Emitter::INTERPRETER, std::string("Added include path: ") + path);
333 } else {
334 LILA_WARN(Emitter::INTERPRETER,
335 std::string("Include path does not exist: ") + path);
336 }
337}
338
339void ClangInterpreter::load_library(const std::string& path)
340{
341 if (!m_impl->interpreter) {
342 LILA_WARN(Emitter::INTERPRETER,
343 std::string("Cannot load library: interpreter not initialized: ") + path);
344 return;
345 }
346 if (auto err = m_impl->interpreter->LoadDynamicLibrary(path.c_str())) {
347 m_last_error = "Failed to load " + path + ": " + llvm::toString(std::move(err));
348 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
349 return;
350 }
351 LILA_DEBUG(Emitter::INTERPRETER, std::string("Loaded library: ") + path);
352}
353
354void ClangInterpreter::add_compile_flag(const std::string& flag)
355{
356 m_impl->compile_flags.push_back(flag);
357 LILA_DEBUG(Emitter::INTERPRETER, std::string("Added compile flag: ") + flag);
358}
359
360void ClangInterpreter::set_target_triple(const std::string& triple)
361{
362 m_impl->target_triple = triple;
363 LILA_INFO(Emitter::INTERPRETER, std::string("Target triple set to: ") + triple);
364}
365
367{
368 LILA_INFO(Emitter::INTERPRETER, "Resetting interpreter");
369 shutdown();
370 m_impl = std::make_unique<Impl>();
371}
372
374{
375 if (m_impl->interpreter) {
376 LILA_INFO(Emitter::INTERPRETER, "Shutting down interpreter");
377 m_impl->interpreter.reset();
378 }
379}
380
381} // namespace Lila
#define LILA_WARN(emitter, msg)
#define LILA_ERROR(emitter, msg)
#define LILA_DEBUG(emitter, msg)
#define LILA_INFO(emitter, msg)
void load_library(const std::string &path)
Load a shared library into the JIT symbol table.
~ClangInterpreter()
Destructor; shuts down the interpreter if active.
EvalResult eval_file(const std::string &filepath)
Evaluates a code file by including it.
EvalResult eval(const std::string &code)
Evaluates a code snippet.
void set_target_triple(const std::string &triple)
Sets the target triple for code generation.
void * get_symbol_address(const std::string &name)
Gets the address of a symbol defined in the interpreter.
ClangInterpreter()
Constructs a ClangInterpreter instance.
std::unique_ptr< Impl > m_impl
Internal implementation details.
std::vector< std::string > get_defined_symbols()
Gets a list of all defined symbols.
void add_compile_flag(const std::string &flag)
Adds a compile flag for code evaluation.
void add_include_path(const std::string &path)
Adds an include path for code evaluation.
void shutdown()
Shuts down the interpreter and releases resources.
void reset()
Resets the interpreter, clearing all state.
Embedded Clang interpreter for live code evaluation in MayaFlux.
static const std::string & get_clang_resource_dir()
static const std::string & find_dep_library(const std::string &library_name, const std::string &prefix="")
static const std::string & get_dep_includes(const std::string &library_name)
static const std::vector< std::string > & get_system_includes()
void initialize()
Definition main.cpp:11
std::string error
Error message if evaluation failed.
std::unordered_map< std::string, void * > symbol_table
std::unique_ptr< clang::Interpreter > interpreter
std::vector< std::string > compile_flags
std::vector< std::string > include_paths