aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-13 03:54:49 +0000
committer_ov4 <ov4@ov4.org>2026-10-13 03:54:49 +0000
commite28cbb2efac9ed6e56cd9ab6eb1a3f16810d575a (patch)
treea0402247ba92259f6b7f3a2ad2c8ed0ca8eab3de
parentba1b65f4a20566405c53d4ded90b483ec9aa7d09 (diff)
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 <noreply@anthropic.com>
-rw-r--r--CMakeLists.txt32
-rw-r--r--cmake/EmbeddedLLVM.cmake26
-rw-r--r--include/cps/CodeGen.h1
-rw-r--r--tools/driver/EmbeddedRunner.cc45
-rw-r--r--tools/driver/EmbeddedRunner.h5
-rw-r--r--tools/driver/main.cc12
6 files changed, 113 insertions, 8 deletions
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<std::unique_ptr<StmtAST>> &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<llvm::SectionMemoryManager>());
+ std::unique_ptr<llvm::ExecutionEngine> 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<int (*)()>(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