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#ifdef MAYAFLUX_PLATFORM_MACOS
144 // CRITICAL: JIT uses Homebrew LLVM but needs macOS SDK for system headers
145 // Homebrew LLVM's libc++ requires pthread.h, sched.h, time.h, etc. from SDK
146 std::string sdk_path = MayaFlux::Platform::SystemConfig::get_macos_sdk_path();
147 if (!sdk_path.empty()) {
148 m_impl->compile_flags.push_back("-isysroot" + sdk_path);
149 LILA_DEBUG(Emitter::INTERPRETER, "Using macOS SDK: " + sdk_path);
150 } else {
151 LILA_WARN(Emitter::INTERPRETER,
152 "Could not find macOS SDK - JIT may fail to find system headers");
153 }
154#endif
155
156 for (const auto& path : m_impl->include_paths) {
157 m_impl->compile_flags.push_back("-I" + path);
158 }
159
160#ifdef MAYAFLUX_PLATFORM_WINDOWS
161 m_impl->compile_flags.emplace_back("-fno-function-sections");
162 m_impl->compile_flags.emplace_back("-fno-data-sections");
163 m_impl->compile_flags.emplace_back("-fno-unique-section-names");
164#endif
165
166 std::vector<const char*> args;
167 for (const auto& flag : m_impl->compile_flags) {
168 args.push_back(flag.c_str());
169 }
170
171 clang::IncrementalCompilerBuilder ICB;
172 ICB.SetCompilerArgs(args);
173
174 auto CI = ICB.CreateCpp();
175 if (!CI) {
176 m_last_error = "Failed to create CompilerInstance: " + llvm::toString(CI.takeError());
177 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
178 return false;
179 }
180
181 auto interp = clang::Interpreter::create(std::move(*CI));
182 if (!interp) {
183 m_last_error = "Failed to create interpreter: " + llvm::toString(interp.takeError());
184 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
185 return false;
186 }
187
188 m_impl->interpreter = std::move(*interp);
189
190 LILA_INFO(Emitter::INTERPRETER, "Clang interpreter created successfully");
191
192 if (skip_host_library_load) {
193 LILA_DEBUG(Emitter::INTERPRETER,
194 "Skipping MayaFluxLib load: host process already resident");
195 } else {
197 "MayaFluxLib", std::string(MayaFlux::Config::INSTALL_PREFIX));
198
199 LILA_DEBUG(Emitter::INTERPRETER, "Loading MayaFlux library: " + lib_to_load);
200
201 if (auto err = m_impl->interpreter->LoadDynamicLibrary(lib_to_load.c_str())) {
202 m_last_error = "Failed to load " + lib_to_load + ": "
203 + llvm::toString(std::move(err));
204 LILA_ERROR(Emitter::INTERPRETER, m_last_error);
205 return false;
206 }
207
208 LILA_DEBUG(Emitter::INTERPRETER, "Loaded " + lib_to_load);
209 }
210
211 const char* header_include = skip_host_library_load
212 ? "#include \"pch.h\"\n"
213 "#include \"MayaFlux/MayaFlux.hpp\"\n"
214 : "#include \"pch.h\"\n"
215 "#include \"Lila/LiveAid.hpp\"\n";
216
217 auto result = m_impl->interpreter->ParseAndExecute(header_include);
218
219 if (result) {
220 std::string warning = "Failed to load MayaFlux headers: " + llvm::toString(std::move(result));
221 LILA_WARN(Emitter::INTERPRETER, warning);
222 } else {
223 LILA_INFO(Emitter::INTERPRETER, "MayaFlux headers loaded successfully");
224 }
225
226 result = m_impl->interpreter->ParseAndExecute("std::cout << \"Ready for Live\" << std::flush;");
227
228 return true;
229}
230
232{
233#ifdef MAYAFLUX_PLATFORM_MACOS
234 __block EvalResult result;
235#else
236 EvalResult result;
237#endif
238
239 if (!m_impl->interpreter) {
240 result.error = "Interpreter not initialized";
241 LILA_ERROR(Emitter::INTERPRETER, result.error);
242 return result;
243 }
244
245 LILA_DEBUG(Emitter::INTERPRETER, "Evaluating code...");
246
247#ifdef MAYAFLUX_PLATFORM_MACOS
248 dispatch_sync(dispatch_get_main_queue(), ^{
249 auto eval_result = m_impl->interpreter->ParseAndExecute(code);
250
251 if (!eval_result) {
252 result.success = true;
253 LILA_DEBUG(Emitter::INTERPRETER, "Code evaluation succeeded");
254 } else {
255 result.success = false;
256 result.error = "Execution failed: " + llvm::toString(std::move(eval_result));
257 LILA_ERROR(Emitter::INTERPRETER, result.error);
258 }
259 });
260
261#elif defined(MAYAFLUX_PLATFORM_WINDOWS)
262 auto completed = std::make_shared<std::atomic<bool>>(false);
263
264 auto* task = static_cast<std::function<void()>*>(
265 HeapAlloc(GetProcessHeap(), 0, sizeof(std::function<void()>)));
266 new (task) std::function<void()>([&, completed]() {
267 auto eval_result = m_impl->interpreter->ParseAndExecute(code);
268
269 if (!eval_result) {
270 result.success = true;
271 LILA_DEBUG(Emitter::INTERPRETER, "Code evaluation succeeded");
272 } else {
273 result.success = false;
274 result.error = "Execution failed: " + llvm::toString(std::move(eval_result));
275 LILA_ERROR(Emitter::INTERPRETER, result.error);
276 }
277
278 completed->store(true, std::memory_order_release);
279 });
280
281 PostThreadMessage(MayaFlux::Parallel::g_MainThreadId, MAYAFLUX_WM_DISPATCH, 0, (LPARAM)task);
282
283 while (!completed->load(std::memory_order_acquire)) {
284 std::this_thread::sleep_for(std::chrono::microseconds(100));
285 }
286
287#else
288 auto eval_result = m_impl->interpreter->ParseAndExecute(code);
289
290 if (!eval_result) {
291 result.success = true;
292 LILA_DEBUG(Emitter::INTERPRETER, "Code evaluation succeeded");
293 } else {
294 result.success = false;
295 result.error = "Execution failed: " + llvm::toString(std::move(eval_result));
296 LILA_ERROR(Emitter::INTERPRETER, result.error);
297 }
298#endif
299
300 m_impl->eval_counter++;
301 return result;
302}
303
305{
306 EvalResult result;
307
308 if (!std::filesystem::exists(filepath)) {
309 result.error = "File does not exist: " + filepath;
310 LILA_ERROR(Emitter::INTERPRETER, result.error);
311 return result;
312 }
313
314 LILA_INFO(Emitter::INTERPRETER, std::string("Evaluating file: ") + filepath);
315
316 std::string code = "#include \"" + filepath + "\"\n";
317 return eval(code);
318}
319
320void* ClangInterpreter::get_symbol_address(const std::string& name)
321{
322 if (!m_impl->interpreter) {
323 LILA_WARN(Emitter::INTERPRETER, "Cannot get symbol: interpreter not initialized");
324 return nullptr;
325 }
326
327 auto symbol = m_impl->interpreter->getSymbolAddress(name);
328 if (symbol) {
329 LILA_DEBUG(Emitter::INTERPRETER, std::string("Found symbol: ") + name);
330 return reinterpret_cast<void*>(symbol->getValue());
331 }
332
333 LILA_WARN(Emitter::INTERPRETER, std::string("Symbol not found: ") + name);
334 return nullptr;
335}
336
338{
339 std::vector<std::string> symbols;
340 for (const auto& pair : m_impl->symbol_table) {
341 symbols.push_back(pair.first);
342 }
343 return symbols;
344}
345
346void ClangInterpreter::add_include_path(const std::string& path)
347{
348 if (std::filesystem::exists(path)) {
349 m_impl->include_paths.push_back(path);
350 LILA_DEBUG(Emitter::INTERPRETER, std::string("Added include path: ") + path);
351 } else {
352 LILA_WARN(Emitter::INTERPRETER,
353 std::string("Include path does not exist: ") + path);
354 }
355}
356
357void ClangInterpreter::add_library_path(const std::string& path)
358{
359 LILA_DEBUG(Emitter::INTERPRETER,
360 std::string("Library path noted (not yet implemented): ") + path);
361}
362
363void ClangInterpreter::add_compile_flag(const std::string& flag)
364{
365 m_impl->compile_flags.push_back(flag);
366 LILA_DEBUG(Emitter::INTERPRETER, std::string("Added compile flag: ") + flag);
367}
368
369void ClangInterpreter::set_target_triple(const std::string& triple)
370{
371 m_impl->target_triple = triple;
372 LILA_INFO(Emitter::INTERPRETER, std::string("Target triple set to: ") + triple);
373}
374
376{
377 LILA_INFO(Emitter::INTERPRETER, "Resetting interpreter");
378 shutdown();
379 m_impl = std::make_unique<Impl>();
380}
381
383{
384 if (m_impl->interpreter) {
385 LILA_INFO(Emitter::INTERPRETER, "Shutting down interpreter");
386 m_impl->interpreter.reset();
387 }
388}
389
390} // namespace Lila
#define LILA_WARN(emitter, msg)
#define LILA_ERROR(emitter, msg)
#define LILA_DEBUG(emitter, msg)
#define LILA_INFO(emitter, msg)
void add_library_path(const std::string &path)
Adds a library path (not yet implemented)
~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