From e28cbb2efac9ed6e56cd9ab6eb1a3f16810d575a Mon Sep 17 00:00:00 2001 From: _ov4 Date: Tue, 13 Oct 2026 03:54:49 +0000 Subject: feat: embed a statically linked LLVM execution engine Add EMBEDDED mode to run generated modules in-process without lli or PATH lookup. Link LLVM and support archives plus the C++ runtime statically, while retaining the system C runtime on Linux. Co-Authored-By: Claude Fable 5.1 --- CMakeLists.txt | 32 +++++++++++++++++++++++------- cmake/EmbeddedLLVM.cmake | 26 ++++++++++++++++++++++++ include/cps/CodeGen.h | 1 + tools/driver/EmbeddedRunner.cc | 45 ++++++++++++++++++++++++++++++++++++++++++ tools/driver/EmbeddedRunner.h | 5 +++++ tools/driver/main.cc | 12 ++++++++++- 6 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 cmake/EmbeddedLLVM.cmake create mode 100644 tools/driver/EmbeddedRunner.cc create mode 100644 tools/driver/EmbeddedRunner.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b2cead4..c311928 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.13) project(cps_compiler) -set(CPSC_RUN_MODE "" CACHE STRING "Execution mode: PATH or NONE (required)") -set_property(CACHE CPSC_RUN_MODE PROPERTY STRINGS PATH NONE) -if(NOT CPSC_RUN_MODE MATCHES "^(PATH|NONE)$") - message(FATAL_ERROR "Specify -DCPSC_RUN_MODE=PATH or -DCPSC_RUN_MODE=NONE") +set(CPSC_RUN_MODE "" CACHE STRING "Execution mode: PATH, EMBEDDED or NONE (required)") +set_property(CACHE CPSC_RUN_MODE PROPERTY STRINGS PATH EMBEDDED NONE) +if(NOT CPSC_RUN_MODE MATCHES "^(PATH|EMBEDDED|NONE)$") + message(FATAL_ERROR "Specify -DCPSC_RUN_MODE=PATH, EMBEDDED or NONE") endif() find_package(LLVM REQUIRED CONFIG) @@ -18,15 +18,15 @@ set(CMAKE_CXX_STANDARD 17) include_directories(include) -add_library(CPSLexer lib/Lexer/Lexer.cc) -add_library(CPSParser +add_library(CPSLexer STATIC lib/Lexer/Lexer.cc) +add_library(CPSParser STATIC lib/Parser/Parser.cc lib/Parser/ParserFunction.cc lib/Parser/ParserFile.cc lib/Parser/ParserType.cc ) -add_library(CPSCodeGen +add_library(CPSCodeGen STATIC lib/CodeGen/CodeGen.cc lib/CodeGen/Designator.cc lib/CodeGen/ArrayHandler.cc @@ -40,9 +40,27 @@ add_library(CPSCodeGen ) llvm_map_components_to_libnames(llvm_libs core support) +if(CPSC_RUN_MODE STREQUAL "EMBEDDED") + llvm_map_components_to_libnames(llvm_libs core support native mcjit) + foreach(llvm_lib IN LISTS llvm_libs) + get_target_property(llvm_type ${llvm_lib} TYPE) + if(NOT llvm_type STREQUAL "STATIC_LIBRARY") + message(FATAL_ERROR "EMBEDDED requires static LLVM libraries: ${llvm_lib}") + endif() + endforeach() + include(cmake/EmbeddedLLVM.cmake) +endif() add_executable(cpsc tools/driver/main.cc) target_compile_definitions(cpsc PRIVATE CPSC_RUN_${CPSC_RUN_MODE}) +if(CPSC_RUN_MODE STREQUAL "EMBEDDED") + target_sources(cpsc PRIVATE tools/driver/EmbeddedRunner.cc) + if(MINGW) + target_link_options(cpsc PRIVATE -static) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND UNIX AND NOT APPLE) + target_link_options(cpsc PRIVATE -static-libstdc++ -static-libgcc) + endif() +endif() target_link_libraries(cpsc CPSCodeGen CPSParser diff --git a/cmake/EmbeddedLLVM.cmake b/cmake/EmbeddedLLVM.cmake new file mode 100644 index 0000000..3b56a3c --- /dev/null +++ b/cmake/EmbeddedLLVM.cmake @@ -0,0 +1,26 @@ +# LLVM packages can attach shared compression/terminal libraries even to +# LLVMSupport.a. Use their archives so these do not become deployment files. +get_target_property(cpsc_support_libs LLVMSupport INTERFACE_LINK_LIBRARIES) +set(cpsc_saved_library_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES}) +set(CMAKE_FIND_LIBRARY_SUFFIXES .a) +foreach(cpsc_dependency IN LISTS cpsc_support_libs) + if(cpsc_dependency STREQUAL "ZLIB::ZLIB") + set(cpsc_library_names z) + elseif(cpsc_dependency MATCHES "^zstd::libzstd") + set(cpsc_library_names zstd) + elseif(cpsc_dependency STREQUAL "Terminfo::terminfo") + set(cpsc_library_names terminfo tinfo curses ncurses ncursesw) + else() + continue() + endif() + list(GET cpsc_library_names 0 cpsc_library_name) + find_library(CPSC_STATIC_${cpsc_library_name} NAMES ${cpsc_library_names}) + if(NOT CPSC_STATIC_${cpsc_library_name}) + message(FATAL_ERROR + "EMBEDDED requires a static archive for ${cpsc_dependency}; install its development package") + endif() + string(REPLACE "${cpsc_dependency}" "${CPSC_STATIC_${cpsc_library_name}}" + cpsc_support_libs "${cpsc_support_libs}") +endforeach() +set(CMAKE_FIND_LIBRARY_SUFFIXES ${cpsc_saved_library_suffixes}) +set_property(TARGET LLVMSupport PROPERTY INTERFACE_LINK_LIBRARIES "${cpsc_support_libs}") diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index ae2f015..2c837fd 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -96,6 +96,7 @@ public: ~CodeGen(); void compile(const std::vector> &Statements); void print(llvm::raw_ostream &Output); + const llvm::Module &getModule() const { return *TheModule; } bool hadError() const { return HadError; } void reportError(const char *Fmt, ...); static bool isBuiltinName(const std::string &Name); diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc new file mode 100644 index 0000000..81a2265 --- /dev/null +++ b/tools/driver/EmbeddedRunner.cc @@ -0,0 +1,45 @@ +#include "EmbeddedRunner.h" +#include "cps/CodeGen.h" +#include "llvm/ExecutionEngine/ExecutionEngine.h" +#include "llvm/ExecutionEngine/MCJIT.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/Transforms/Utils/Cloning.h" + +int runEmbedded(const cps::CodeGen &CG) { + if (llvm::InitializeNativeTarget() || llvm::InitializeNativeTargetAsmPrinter()) { + llvm::errs() << "Error: cannot initialize the embedded LLVM target\n"; + return 1; + } + std::string Error; + if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr, &Error)) { + llvm::errs() << "Error: cannot load process symbols: " << Error << "\n"; + return 1; + } + // Keep CodeGen's module and context alive while the engine owns its clone. + auto Module = llvm::CloneModule(CG.getModule()); + Module->setTargetTriple(llvm::sys::getProcessTriple()); + llvm::EngineBuilder Builder(std::move(Module)); + Builder.setEngineKind(llvm::EngineKind::JIT); + Builder.setErrorStr(&Error); + Builder.setMCJITMemoryManager(std::make_unique()); + std::unique_ptr Engine(Builder.create()); + if (!Engine) { + llvm::errs() << "Error: cannot create the embedded LLVM engine: " << Error << "\n"; + return 1; + } + Engine->finalizeObject(); + if (Engine->hasError()) { + llvm::errs() << "Error: embedded LLVM execution failed: " << Engine->getErrorMessage() << "\n"; + return 1; + } + auto MainAddress = Engine->getFunctionAddress("main"); + if (!MainAddress) { + llvm::errs() << "Error: embedded LLVM could not resolve main\n"; + return 1; + } + auto Main = reinterpret_cast(MainAddress); + return Main(); +} diff --git a/tools/driver/EmbeddedRunner.h b/tools/driver/EmbeddedRunner.h new file mode 100644 index 0000000..1651f4d --- /dev/null +++ b/tools/driver/EmbeddedRunner.h @@ -0,0 +1,5 @@ +#pragma once + +namespace cps { class CodeGen; } + +int runEmbedded(const cps::CodeGen &CG); diff --git a/tools/driver/main.cc b/tools/driver/main.cc index f5373a0..4df8562 100644 --- a/tools/driver/main.cc +++ b/tools/driver/main.cc @@ -1,6 +1,9 @@ #include "cps/Lexer.h" #include "cps/Parser.h" #include "cps/CodeGen.h" +#ifdef CPSC_RUN_EMBEDDED +#include "EmbeddedRunner.h" +#endif #include "llvm/ADT/SmallString.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FileUtilities.h" @@ -48,7 +51,9 @@ int main(int argc, char **argv) { llvm::errs() << "Error: no source file specified\n"; return 1; } +#ifdef CPSC_RUN_PATH std::string LliPath; +#endif if (Run) { #ifdef CPSC_RUN_PATH auto Lli = llvm::sys::findProgramByName("lli"); @@ -57,7 +62,7 @@ int main(int argc, char **argv) { return 1; } LliPath = *Lli; -#else +#elif defined(CPSC_RUN_NONE) llvm::errs() << "Error: -r is disabled in this build (CPSC_RUN_MODE=NONE)\n"; return 1; #endif @@ -93,6 +98,9 @@ int main(int argc, char **argv) { if (CG.hadError()) return 1; +#ifdef CPSC_RUN_EMBEDDED + if (Run && OutputPath.empty()) return runEmbedded(CG); +#endif std::error_code EC; int OutputFD; llvm::FileRemover TemporaryOutput; @@ -137,6 +145,8 @@ int main(int argc, char **argv) { return 1; } return Result; +#elif defined(CPSC_RUN_EMBEDDED) + return runEmbedded(CG); #else return 0; #endif -- cgit v1.3.1-4-g156e